Django ORM
ORM(Object Relational Mapping):对象关系映射,描述Django数据模型类和数据库之间的映射关系,通俗的讲就是让一个类和一个数据库表进行对应,这使ORM在数据库层和业务逻辑层之间起到桥梁作用。
Django通过类代码描述数据表字段、表间关系等内容,并通过相应命令把类所描述的内容持久化到数据库。
Django ORM的模式特征
Django ORM与数据库映射的关系表现为django中的一个数据模型(Model)映射一个数据库表。其基本情况是:类映射到数据库表,类的属性映射为数据库表字段,类的实例对象则映射为数据行。
Django ORM能实现的功能:一是生成数据库表,如数据库表的创建、修改、删除;二是操作数据库表的数据行,如数据行的增删改查。但不能创建数据库。
Django ORM使用步骤主要有:
- 在项目使用的数据库管理系统中建立数据库。
- 在项目的配置文件settings中设置数据库的连接字符。
- 在应用程序的models文件编写继承于models.Model的数据模型。
- 运行python manage.py makemigrations 和 python manage.py migrate 两个命令生成数据库表。
- 使用django ORM操作数据库表。
Django ORM的用法
数据库连接
以MySQL为例,先创建test_orm数据库,在settings文件中配置:
DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1',
'PORT': '3306',
'NAME': 'test_orm',
'USER': 'root',
'PASSWORD': '1234',
}
}
创建数据模型
创建一个group模型,有两个属性,即user和email。在models.py中输入以下代码,假设在项目中有一个应用名称为employee。
# 必须导入数据模型相关的模块
from django.db import models
# 数据模型一定要继承于models.Model
class Group(models.Model):
# group_name为团体名称,CharField为类型,max_length设置最大字符数
# verbose_name设置在django Admin管理后台页面上显示的字段名
group_name = models.CharField(max_length=32,verbose_name='团体名称')
# 团体名称备注
group_script = models.CharField(max_length=60,verbose_name='备注')
group_name和group_script可以称作模型字段,每个字段在类中表现为一个类属性,根据映射关系,每个类属性映射为一个数据表字段。
在命令行输入python manage.py makemigrations 和 python manage.py migrate生成数据表。
Django ORM字段
Django ORM字段在models中创建,按照固定格式在数据模型类中建立,主要包括指定字段名的字段类型、字段属性等。
常用字段类型
-
CharField:字符类型,必须提供max_length参数,表示字符长度。verbose_name在Django Admin管理后台是字段的显示名称,可理解为字段别名,SQL层面没有具体体现,对数据库中的字段没有影响。
name = models.CharField(max_length=32,verbose_name='名称')
-
EmailField:邮箱类型,实际上是字符类型,只是提供了邮箱格式的校验。
email = models.EmailField(verbose_name='邮箱')
-
TextField:文本类型,存储大段文本字符串。
descript = models.IntegerField(verbose_name='简介')
-
IntegerField:整数类型。
int = models.IntegerField()
-
DateField:日期字段
date = models.DateField(auto_now=True,auto_now_add=False)
auto_now参数自动保存当前时间,一般用来表示最后修改时间。在第一次创建记录的时候,django就将auto_now_add字段值自动设置为当前时间,用来表示记录对象的创建时间。
-
TimeField:时间字段
time = models.TimeField(auto_now=False,auto_now_add=False)
-
DateTimeField:日期时间字段,合并了日期字段与时间字段。
datetime = models.DateTimeField(auto_now=False,auto_now_add=False)
-
FileField:实际上是字符串类型,用来把上传的文件的路径保存在数据库中。文件上传到指定目录,主要参数upload_to指明上传文件的保存路径,这个路径与django配置文件的MEDIA_ROOT有关。
filetest = models.FileField(upload_to = 'tets/')
如果MEDIA_ROOT = os.path.join(BASE_DIR,'upload/')这句代码设定MEDIA_ROOT值为/test_orm/upload/,假设在数据表中filetest的值是test.txt,那么文件路径为/test_orm/upload/test/test.txt。
-
ImageField
picture = models.ImageField(upload_to = 'pic/')
常用字段属性
-
db_index:等于True表示设置此字段为数据库表的索引。
title = models.CharField(max_length=32,db_index=True)
-
unique:等于True表示该字段在数据库表中不能有重复值。
-
default:设置字段默认值,如default=’good’。
-
auto_now_add:是DateTimeField、TimeField、DateField的独有属性,等于True表示把新建该记录的时间保存为该字段的值。
-
auto_now:是DateTimeField、TimeField、DateField的独有属性,等于True表示每次修改记录时,把当前时间存储到该字段。
Django ORM基本数据操作
通过数据模型的objects属性来提供数据操作的接口。
-
增加记录
# 第一种方式 new_emp = models.employee.objects.create(name = "tom",email = "tom123@163.com",dep_id = 66) # 第二种方式 new_emp = models.employee(name = "tom",email = "tom123@163.com",dep_id=66) new_emp.save()
-
删除记录,用filter()过滤出符合条件的记录后调用delete()删除。
models.employee.objects.filter(name='张三').delete()
-
修改记录
# 将指定条件的记录更新,并更新指定字段的值 models.employee.objects.filter(name='tom').update(email='tom1234@163.com') # 修改单条数据 obj = models.employee.objects.get(id=66) obj.email = 'tom1234@sine.com' obj.save()
-
查询
# 查询全部 emp_list = models.employee.objects.all() # 获取单条数据,数据不存在则报错 emp=models.employee.objects.get(id=123) # 获取指定条件的记录集 emp_group = models.employee.objects.filter(name='张三')
Django ORM数据库操作常用函数
下面列举的5个函数的返回值都是QuerySet对象集。
-
all()函数,返回符合条件的全部记录。
objects = models.employee.objects.all()
-
filter()函数,返回指定条件的记录。filter后面的括号内是过滤条件。
objects = models.employee.objects.filter(name='tom')
过滤一般用“字段名+双下划线+条件名词”。
# 获取name字段包含tom的记录 models.employee.objects.filter(name__contains="tom") # 获取name字段包含tom的记录,忽略大小写 models.employee.objects.filter(name__icontains="tom") # 获取employee数据表中id=10、20、66的数据 models.employee.objects.filter(id__in=[10,20,66]) # 获取employee数据表中id不等于10、20、66的数据 models.employee.objects.exclude(id__in=[10,20,66]) # 获取employee数据表中id大于1且小于10的记录 models.employee.objects.filter(id_gt=1,id_lt=10) # 获取employee数据表中id在1~66内的记录 models.employee.objects.filter(id_range=[1,66]) # 获取employee数据表中birthday字段月份为9的记录 models.employee.objects.filter(birthday_month=9)
-
exclude()函数,返回不符合括号内条件的记录,和filter相反。
objects = models.employee.objects.exclude(name='tom')
-
order_by()函数,按照括号内的字段排序。
objects = models.employee.objects.exclude(name='tom').order_by('name','id')
字段名中加-,表示按该字段倒序排序。如下按name字段倒序排列列表。
objects = models.employee.objects.order_by('-name')
-
distinct()函数,去掉记录集合中完全一样的记录再返回。
objects = models.employee.objects.filter(name='tom').distinct()
以下三个函数返回其他数据类型,可以理解为特殊的QuerySet类型。
-
values()函数,返回一个字典类型序列。
objects = models.employee.objects.values('id','name','email') <QuerySet [{'id':1,'name':'大华','email':'1@1.com'},{'id':2,'name':'大d','email':'1@2.com'}] >
-
values_list()函数,返回一个元组类型序列,返回一个元组类型序列。
objects = models.employee.objects.values_list('id','name','email') <QuerySet [('id':1,'name':'大华','email':'1@1.com'),('id':2,'name':'大d','email':'1@2.com')] >
-
get()、first()、last()返回单个对象,可以理解为返回数据表中的一条记录。
# 返回id为1的记录,括号内是过滤条件 object1=models.employee.objects.get(id=1) # 返回数据集的第一条记录 object2=models.employee.objects.first() # 返回数据集的最后一条记录 object3=models.employee.objects.last() # 返回数据集的个数 object4=models.employee.objects.count()
样例1:数据库表操作
准备工作
-
先建立一个test_orm项目。输入django-admin startproject test_orm,生成项目。这个test_orm就是项目的根目录,里面包括一个test_orm/项目目录和一个manage.py,里面包括生成django应用程序、数据库表等命令集。
-
在命令行终端输入python manage.py startapp employee,生成一个应用程序。
-
以MySQL作为后台数据库,新建数据库名称为test_orm。
-
如果没有pymysql模块先安装,然后在/test_orm//test_orm/__init__.py文件中输入以下代码,使用pymysql代替Django MySQL客户端模块。
import pymysql pymysql.install_as_MySQLdb()
-
在settings里修改配置,一是在INSTALLED_APPS里加入employee,二是在DATABASES里把数据库配置为MySQL类型:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'HOST': '127.0.0.1', 'PORT': '3306', 'NAME': 'test_orm', 'USER': 'root', 'PASSWORD': '1234', } }
-
在models中输入数据模型代码:
from django.db import models # 员工数据模型(员工数据表) class employee(models.Model): name = models.CharField(max_length=32,verbose_name='姓名') email = models.EmailField(verbose_name='邮箱') # 员工部门,Foreignkey类型,与department表中记录形成多对一关系 # on_delete = models.CASCADE表示如果外键所关联的department表中的一条记录被删除 # 本表中与这条记录有关的记录将全被删除 # 有外键的表是多,关联的表是一 # 外键在数据库表中以外键名_id形式命名字段 dep = models.ForeignKey(to="department",on_delete=models.CASCADE) # 员工加入的团体,多对多关系 group = models.ManyToManyField(to="group") salary = models.DecimalField(max_digits=8, decimal_places=2) info = models.OneToOneField(to="employeeinfo",on_delete=models.CASCADE,null=True) # 部门数据模型(部门数据表) class department(models.Model): dep_name = models.CharField(max_length=32,verbose_name='部门名称') dep_script = models.CharField(max_length=60, verbose_name='备注') # 团体数据表 class group(models.Model): group_name = models.CharField(max_length=32,verbose_name='团体名称') group_script = models.CharField(max_length=60,verbose_name='备注') # 员工补充信息数据表 class employeeinfo(models.Model): phone = models.CharField(max_length=11) address = models.CharField(max_length=50)
-
终端输入python manage.py makemigrations和python manage.py migrate生成数据表,第一个命令对数据模型代码进行检查,出错时返回相关信息,第二个命令真正建立数据表。
提示:如果出错,去应用里的migrations里面删除__pycache__和0001_initial即可。
建立路由与视图函数对应关系
Django是在urls中设立路由与视图函数的对应关系。用户在浏览器地址栏中输入网址,django通过URL配置关系找到对应函数,这个函数接受请求,运行其中的逻辑代码并生成响应发回浏览器,从而完成一次用户业务访问过程。
为了层次清晰,我们建立两级URL配置文件。先建一级配置文件,在urls中输入:
urlpatterns = [
path('admin/', admin.site.urls),
# 用include()函数把二级配置包含进来
path('test_orm_old/', include('employee.urls')),
]
说明:
- include函数的参数是一个字符串,这个字符串指定二级URL配置文件的位置,用点作为分隔符,并且最后的文件名不包含扩展名。
- 如果URL配置文件分级,那么在匹配URL时,要把各级配置文件中URL表达式合并成一个完整的URL表达式进行匹配。
在employee文件夹下新建一个urls文件,输入:
from django.urls import path,include
# 导入视图函数
from employee.views import *
urlpatterns = [
# 操作员工数据表相关URL配置项
path('list_employee_old/',list_employee_old),
path('add_employee_old/',add_employee_old),
path('edit_employee_old/<int:emp_id>/',edit_employee_old),
path('del_employee_old/<int:emp_id>/',delete_employee_old),
# 操作部门数据表相关URL配置项
path('add_dep_old/',add_dep_old),
path('list_dep_old/',list_dep_old),
path('del_dep_old/<int:dep_id>/',del_dep_old),
path('edit_dep_old/<int:dep_id>/',edit_dep_old),
# 操作团体数据表相关URL配置项
path('add_group_old/',add_group_old),
path('list_group_old/',list_group_old),
path('del_group_old/<int:group_id>/',del_group_old),
path('edit_group_old/<int:group_id>/',edit_group_old),
# 操作员工补充信息数据表相关URL配置项
path('add_employeeinfo_old/', add_employeeinfo_old),#增加一条用户补充信息(employeeinfo表)
path('list_employeeinfo_old/', list_employeeinfo_old),#用户补充信息列表(employeeinfo表)
path('del_employeeinfo_old/<int:info_id>/', del_employeeinfo_old),#删除一条用户补充信息(employeeinfo表)
path('edit_employeeinfo_old/<int:info_id>/', edit_employeeinfo_old),#修改一条用户补充信息(employeeinfo表)
]
说明:
- 以上代码分别建立员工、部门、团体、员工补充信息的增删改查配置项。path()的两个参数一个用来匹配路径,被称作URL表达式,它匹配网址的方式类似于正则表达式;另一个是视图函数名,在views中定义。
- 我们在URL表达式和视图函数名后面都加了_old,因为这是初始版本,没有对页面进行美化,后续会有新样例。
编写视图函数
在urls中建立对应关系后还需建立相应的视图函数,函数名与path第二个参数相同。
打开views,编写对部门数据表department进行列表查询。
from django.shortcuts import render,redirect,HttpResponse
from .models import employee,department,group,employeeinfo
# 对部门数据表的增删改查
def list_dep_old(request):
# 取得数据库表全部记录
dep_list = department.objects.all()
return render(request,'test_orm_old/list_dep_old.html',{'dep_list':dep_list})
说明:
首先导入模块和4个数据模型。dep_list保存部门表的全部记录,再通过render函数发送给list_dep_old.html。render有三个参数,第一个request是固定的,第二个是html文件,第三个是字典类型,这个参数传值给html文件,在网页中以模板变量形式放置在相应的位置。
下面是list_dep_old.html文件的代码,存放在templates/list_dep_old文件夹中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>部门列表</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<div align="center">
<h1>部门列表</h1>
<hr>
<div><a href="/test_orm_old/add_dep_old/">增加一条记录</a></div>
<table border="1">
<thead>
<tr>
<td>部门名称</td>
<td>备注说明</td>
<td colspan="2">操作</td>
</tr>
</thead>
<tbody>
{% for dep in dep_list %}
<tr>
<td>{{ dep.dep_name }}</td>
<td>{{ dep.dep_script }}</td>
<td><a href="/test_orm_old/del_dep_old/{{ dep.id }}/">删除</a></td>
<td><a href="/test_orm_old/edit_dep_old/{{ dep.id }}">修改</a></td>
</tr>
{% empty %}
<tr>
<td colspan="4">无相关记录!</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
说明:
-
视图函数传入的变量dep_list是一个QuertSet对象,它是一个数据集,实际包含数据表中一行一行的记录,所以在HTML文件中用{% for dep in dep_list %}取出每一行记录存在dep中,然后通过{{ dep.字段名 }}取出每个字段的值。
{% for dep in dep_list %}......{% empty %}是模板标签,是一个循环语句代码块。
-
<td><a href="/test_orm_old/del_dep_old/{{ dep.id }}/">删除</a></td>中的{{ dep.id }}获取department数据表的id值。/test_orm_old/与一级URL配置文件/test_orm/test_orm/urls.py中path('test_orm_old/', include('employee.urls'))配置项有关,它匹配path函数的第一个参数;del_dep_old/{{ dep.id }}/与二级URL配置文件/test_orm/employee/urls.py中path('del_dep_old/<int:dep_id>/',del_dep_old)配置项有关,它匹配path函数第一个参数。可以理解为单击这个a标签,django会用views中的del_dep_old视图函数,这个函数由path函数第二个参数指定。
总结:实现以上功能需要在urls、views中编写代码,还需建立一个HTML文件:
- 在一级配置文件的urls中加入path('test_orm_old/', include('employee.urls'))配置项。
- 在二级配置文件的urls中加入path('list_dep_old/',list_dep_old)配置项。
- 在views中建立视图函数def list_dep_old(request),编写逻辑代码。
- 在templates/list_dep_old文件夹下建立list_dep_old.html。
开启django,地址栏输入http://127.0.0.1:8000/test_orm_old/list_dep_old/
增加部门视图函数add_dep_old()的代码如下:
def add_dep_old(request):
# 判断请求方式,如果是post,说明前端页面要提交数据
if request.method == 'POST':
dep_name = request.POST.get('dep_name')
dep_script = request.POST.get('dep_script')
if dep_name.strip() == '':
return render(request, 'test_orm_old/add_dep_old.html', {'error_info':'部门名称不能为空'})
try:
# 用create函数新建一条记录,这条记录会自动保存,不用调用save函数
p = department.objects.create(dep_name=dep_name,dep_script=dep_script)
return redirect('/test_orm_old/list_dep_old/')
except Exception as e:
return render(request,'test_orm_old/add_dep_old.html',{'error_info':'输入部门名称重复或信息有误'})
finally:
pass
return render(request,'test_orm_old/add_dep_old.html')
说明:
提交数据一般用POST,通过request.POST.get取得HTML文件中form的input标签中的值,request.POST.get函数中的参数就是HTML文件中input标签的name属性。
代码把request.POST.get取得的值传递给django orm的create函数,生成一条数据记录。代码中p = department.objects.create(dep_name=dep_name,dep_script=dep_script)也可以用下面两种方式代替:
# 一:
obj = department(dep_name=dep_name,dep_script=dep_script)
obj.save()
# 二:
dic = {"dep_name":dep_name,"deo_script":dep_script}
department.create(**dic)
在生成新的记录之后,通过return redirect('/test_orm_old/list_dep_old/')重定向到部门列表页面;redirect函数的参数是一个字符串,匹配的是URL配置项,不是HTML文件名。即views的list_deo_old视图函数。
add_dep_old.html文件:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>增加部门</title>
</head>
<body>
<div align="center">
<h1>增加部门</h1>
<hr>
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="id" id="id" value="{{ department.id }}" >
<div>
<label>部门:</label>
<input type="text" name="dep_name" id="dep_name" >
</div>
<br>
<div>
<label>备注:</label>
<input type="text" name="dep_script" id="dep_script" >
</div>
<br>
<div><input type="submit" value="保存"></div>
</form>
{{ error_info }}
</div>
</body>
</html>
说明:
form标签中的每个input标签中的输入的值会随着POST请求传给视图函数,视图函数通过request.POST.get('xxx')取得输入的值,xxx是input标签name的值。
{% csrf_token %}是为了防止CSRF所做的保护,是一种安全机制。
下面是删除部门记录的视图函数。根据传入参数,获取id字段等于参数值的记录对象,然后删除这个对象。视图函数del_dep_old的第二个参数是在urls文件配置项的path函数中定义的。而path('del_dep_old/<int:dep_id>/', del_dep_old)中的int指明数据类型,dep_id指明视图函数del_dep_old()第二个参数的名称。参数值来自部门列表文件list_dep_old.html中的{{ dep.id }}。
def del_dep_old(request,dep_id):
dep_object = department.objects.get(id=dep_id)
dep_object.delete()
return redirect('/test_orm_old/list_dep_old/')
视图函数edit_dep_old()实现修改功能。首先判断请求方式是不是POST,是则通过dep_object=department.objects.get(id=id)取出数据表记录,然后给每一个字段赋值,这些值是POST请求传递过来的。如果不是POST请求就推断是第一次请求页面,首先根据参数dep_id(来自部门列表文件list_dep_old.html中的{{ dep.id }}取出记录放到变量dep_object中,通过render函数传递到edit_dep_old.html文件中,代码如下:
def edit_dep_old(request, dep_id):
if request.method == 'POST':
id = request.POST.get('id')
# 获取前端页面提交的数据
dep_name = request.POST.get('dep_name')
dep_script = request.POST.get('dep_script')
dep_object = department.objects.get(id=id)
# 给字段赋值
dep_object.dep_name = dep_name
dep_object.dep_script = dep_script
# 保存数据到数据表
dep_object.save()
return redirect('/test_orm_old/list_dep_old/')
else:
dep_object = department.objects.get(id=dep_id)
return render(request,'test_orm_old/edit_dep_old.html',{'department':dep_object})
视图函数edit_dep_old通过render(request,'test_orm_old/edit_dep_old.html',{'department':dep_object})传递参数给edit_dep_old.html文件,这个文件部分代码如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改部门</title>
</head>
<body>
<div align="center">
<h1>修改部门</h1>
<hr>
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="id" id="id" value="{{ department.id }}" >
<div>
<label>部门:</label>
<input type="text" name="dep_name" id="dep_name" value="{{ department.dep_name }}">
</div>
<br>
<div>
<label>备注:</label>
<input type="text" name="dep_script" id="dep_script" value="{{ department.dep_script }}">
</div>
<br>
<div><input type="submit" value="保存"></div>
</form>
{{ error_info }}
</div>
</body>
</html>
说明:
- 以上代码与add_dep_old.html相似,只是在input标签中给value属性进行了赋值。
- 隐含的input标签用于保存数据库表记录主键id的值,并提供给视图函数修改记录。
以上是部门数据表增删查改功能的实现,总结如下:
- 在urls中建立URL与视图函数的一一对应关系。
- 在views中建立函数,实现业务逻辑,传递变量给HTML。
- 在HTML文件设计网页结构,接收视图函数传递变量,通过模板语言进行渲染,形成用户需要的页面。
group团体数据库表的增删改查视图函数代码如下:
#团队增删改查
def list_group_old(request):
group_list=group.objects.all()
return render(request,'test_orm_old/list_group_old.html',{'group_list':group_list})
def add_group_old(request):
if request.method=='POST':
group_name=request.POST.get('group_name')
group_script = request.POST.get('group_script')
if group_name.strip()=='':
return render(request, 'test_orm_old/add_group.html', {'error_info': '团队名称不能为空!'})
try:
group.objects.create(group_name=group_name,group_script=group_script)
return redirect('/test_orm_old/list_group_old/')
except Exception as e:
return render(request, 'test_orm_old/add_group_old.html',{'error_info':'录入团队名称重复或信息有错误!'})
finally:
pass
return render(request, 'test_orm_old/add_group_old.html')
def del_group_old(request,group_id):
group_object=group.objects.get(id=group_id)
group_object.delete()
return redirect('/test_orm_old/list_group_old/')
def edit_group_old(request,group_id):
if request.method=='POST':
id=request.POST.get('id')
group_name=request.POST.get('group_name')
group_script=request.POST.get('group_script')
group_object=group.objects.get(id=id)
group_object.group_name=group_name
group_object.group_script=group_script
group_object.save()
return redirect('/test_orm_old/list_group_old/')
else:
group_object=group.objects.get(id=group_id)
return render(request,'test_orm_old/edit_group_old.html',{'group':group_object})
以下是list_group_old.html代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>团队列表</title>
</head>
<body>
<div align="center">
<h1>团队列表</h1>
<hr>
<div><a href="/test_orm_old/add_group_old/">增加一条记录</a></div>
<table border="1">
<thead>
<tr>
<td>团队名称</td>
<td>备注说明</td>
<td colspan="2">操作</td>
</tr>
</thead>
<tbody>
{% for group in group_list %}
<tr>
<td>{{ group.group_name }}</td>
<td>{{ group.group_script }}</td>
<td><a href="/test_orm_old/del_group_old/{{ group.id }}/">删除</a></td>
<td><a href="/test_orm_old/edit_group_old/{{ group.id }}/">修改</a></td>
</tr>
{% empty %}
<tr>
<td colspan="4">无相关记录!</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
以下是add_group_old.html代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>增加团队</title>
</head>
<body>
<div align="center">
<h1>增加团队</h1>
<hr>
<form action="/test_orm_old/add_group_old/" method="post">
{% csrf_token %}
<div>
<label>团队:</label>
<input type="text" name="group_name" id="group_name">
</div>
<br>
<div>
<label>备注:</label>
<input type="text" name="group_script" id="group_script">
</div>
<br>
<div><input type="submit" value="增加"></div>
</form>
<div style="color:red;">{{ error_info }}</div>
</div>
</body>
</html>
以下是edit_group_old.html代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改团队</title>
</head>
<body>
<div align="center">
<h1>修改团队</h1>
<hr>
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="id" id="id" value="{{ group.id }}" >
<div>
<label>团队:</label>
<input type="text" name="group_name" id="group_name" value="{{ group.group_name }}">
</div>
<br>
<div>
<label>备注:</label>
<input type="text" name="group_script" id="group_script" value="{{ group.group_script }}">
</div>
<br>
<div><input type="submit" value="保存"></div>
</form>
{{ error_info }}
</div>
</body>
</html>
employeeinfo员工补充信息表的视图函数代码如下。employee和employeeinfo两表有一对一关系,实际上相当于一个表分到了两个地方,这样的原因主要是表的字段访问频率不同,因此把访问频率高的字段放在一个表中。
#employeeinf增删改查
def list_employeeinfo_old(request):#员工补充信息列表
info_list=employeeinfo.objects.all()
return render(request,'test_orm_old/list_employeeinfo_old.html',{'info_list':info_list})
def add_employeeinfo_old(request):#增加一条员工补充信息记录
if request.method=='POST':
phone=request.POST.get('phone')
address = request.POST.get('address')
if phone.strip()=='':
return render(request, 'test_orm_old/add_employeeinfo_old.html', {'error_info': '电话不能为空!'})
try:
employeeinfo.objects.create(phone=phone,address=address)
return redirect('/test_orm_old/list_employeeinfo_old/')
except Exception as e:
return render(request, 'test_orm_old/add_employeeinfo_old.html',{'error_info':'信息有错误!'})
finally:
pass
return render(request, 'test_orm_old/add_employeeinfo_old.html')
def del_employeeinfo_old(request,info_id):#删除一条员工补充信息记录
info_object=employeeinfo.objects.get(id=info_id)
info_object.delete()
return redirect('/test_orm_old/list_employeeinfo_old/')
def edit_employeeinfo_old(request,info_id):#修改一条员工补充信息记录
if request.method=='POST':
id=request.POST.get('id')
phone = request.POST.get('phone')
address = request.POST.get('address')
info_object=employeeinfo.objects.get(id=id)
info_object.phone=phone
info_object.address=address
info_object.save()
return redirect('/test_orm_old/list_employeeinfo_old/')
else:
info_object=employeeinfo.objects.get(id=info_id)
return render(request,'test_orm_old/edit_employeeinfo_old.html',{'info':info_object})
以下是list_employeeinfo_old.html代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div align="center">
<h1>人员地址电话列表</h1>
<hr>
<div><a href="/test_orm_old/add_employeeinfo_old/">增加一条记录</a></div>
<table border="1">
<thead>
<tr>
<td>电话</td>
<td>地址</td>
<td colspan="2">操作</td>
</tr>
</thead>
<tbody>
{% for info in info_list %}
<tr>
<td>{{ info.phone }}</td>
<td>{{ info.address }}</td>
<td><a href="/test_orm_old/del_employeeinfo_old/{{ info.id }}/">删除</a></td>
<td><a href="/test_orm_old/edit_employeeinfo_old/{{ info.id }}/">修改</a></td>
</tr>
{% empty %}
<tr>
<td colspan="4">无相关记录!</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
以下是add_employeeinfo_old.html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div align="center">
<h1>增加用户电话地址</h1>
<hr>
<form action="/test_orm_old/add_employeeinfo_old/" method="post">
{% csrf_token %}
<div>
<label>电话号码:</label>
<input type="text" name="phone" id="phone">
</div>
<br>
<div>
<label>家庭住址:</label>
<input type="text" name="address" id="address">
</div>
<br>
<div><input type="submit" value="增加"></div>
</form>
<div style="color:red;">{{ error_info }}</div>
</div>
</body>
</body>
</html>
以下是edit_employeeinfo_old.html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div align="center">
<h1>修改电话地址</h1>
<hr>
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="id" id="id" value="{{ info.id }}" >
<div>
<label>电话:</label>
<input type="text" name="phone" id="phone" value="{{ info.phone }}">
</div>
<br>
<div>
<label>地址:</label>
<input type="text" name="address" id="address" value="{{ info.address }}">
</div>
<br>
<div><input type="submit" value="保存"></div>
</form>
{{ error_info }}
</div>
</body>
</html>
employee数据模型的操作
employee数据模型中有外键、多对多键、一对一键,对它的数据操作有个别不同之处,以下是员工数据表的删除操作的视图函数:
def list_employee_old(request):
emp=employee.objects.all()
return render(request,'test_orm_old/list_employee_old.html',{'emp_list':emp})
def delete_employee_old(request,emp_id):
emp=employee.objects.get(id=emp_id)
emp.delete()
return redirect('/test_orm_old/list_employee_old')
list_employee_old.html文件如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>员工列表</title>
</head>
<body>
<div align="center">
<h1>员工列表</h1>
<hr>
<div><a href="/test_orm_old/add_employee_old/">增加一条记录</a></div>
<table border="1">
<thead>
<tr>
<td>姓名</td>
<td>邮件</td>
<td>薪水</td>
<td>地址</td>
<td>部门</td>
<td>团队</td>
<td colspan="2">操作</td>
</tr>
</thead>
<tbody>
{% for emp in emp_list %}
<tr>
<td>{{ emp.name }}</td>
<td>{{ emp.email }}</td>
<td>{{ emp.salary }}</td>
<td>{{ emp.info.address }}</td>
<td>{{ emp.dep.dep_name }}</td>
<td>
{% for gp in emp.group.all %}
{% if forloop.last %}
{{ gp.group_name }}
{% else %}
{{ gp.group_name }},
{% endif %}
{% endfor %}
</td>
<td><a href="/test_orm_old/del_employee_old/{{ emp.id }}/">删除</a></td>
<td><a href="/test_orm_old/edit_employee_old/{{ emp.id }}/">修改</a></td>
</tr>
{% empty %}
<tr>
<td colspan="7">无相关记录!</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
说明:
- emp_list是视图函数传过来的变量,是一个Django QuerySet对象集,用{% for emp in emp_list %}取出每一个对象放到emp中,这样emp对象称为employee数据模型的实例化对象,外键dep、多对多键group、一对一键info这些关联关系也包含在emp对象中,因为Django ORM会自动把关联关系也放在Query Set对象中。
- 在模板语法{{ emp.dep }}中可通过dep这个外键取得与它关联的department数据表中的一条记录,{{ emp.dep.dep_name }}可取得department数据表中关联的dep_name字段的值。
- 同理{{ emp.group }}通过group这个多对多键可取得group数据表中的相关联的纪录,由于是多对多关系,这些关联的纪录不止一条,所以要用{% for gp in emp.group.all %}把记录一条条取出来放在gp中,这样{{ gp.group_name }}就可显示团体名称了。
- <a href='test_orm_old/del_employee_old/{{ emp.id }}/'>删除</a>中的del_employee_old/{{ emp.id }}/匹配urls文件的配置项path('del_employee_old/<int:emp_id>/',delete_employee_old)语句中path函数的第一个参数。
增加员工记录视图函数的代码:
def add_employee_old(request):
if request.method=="POST":
name=request.POST.get("name")
email=request.POST.get("email")
dep=request.POST.get("dep")
info = request.POST.get("info")
salary = request.POST.get("salary")
groups=request.POST.getlist("group")
new_emp=employee.objects.create(name=name,email=email,salary=salary,dep_id=dep,info_id=info)
new_emp.group.set(groups)
return redirect('/test_orm_old/list_employee_old/')
dep_list=department.objects.all()
group_list=group.objects.all()
info_list = employeeinfo.objects.all()
return render(request,'test_orm_old/add_employee_old.html',{'dep_list':dep_list,'group_list':group_list,'info_list':info_list})
说明:
- employee数据库表有个多对多键group,要用getlist()取值。
- 外键在数据表中产生的字段名为外键名_id。因此在new_emp=employee.objects.create(name=name,email=email,salary=salary,dep_id=dep,info_id=info)语句中可以直接把变量值赋给dep_id和info_id。
- 多对多键group涉及多个值,因此在生成一条记录new_emp后要通过new_emp.group.set(groups)进行赋值。
- 如果是第一次打开网页需要将dep_list、group_list、info_list变量传递给HTML文件,通过render传到网页,通过语言模板放到select标签供用户使用。
add_employee_old.html如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div align="center">
<h1>增加员工</h1>
<hr>
<form action="/test_orm_old/add_employee_old/" method="post">
{% csrf_token %}
<div>
<label>姓名:</label>
<input type="text" name="name" id="name">
</div>
<br>
<div>
<label>邮箱:</label>
<input type="text" name="email" id="email">
</div>
<br>
<div>
<label>工资:</label>
<input type="text" name="salary" id="salary">
</div>
<br>
<div>
<label>电话地址:</label>
<select name="info" id="info">
{% for info in info_list %}
<option value="{{ info.id}}"> {{ info.phone }}||{{ info.address }}</option>
{% endfor %}
</select>
</div>
<br>
<div>
<label>部门:</label>
<select name="dep" id="dep">
{% for dep in dep_list %}
<option value="{{ dep.id}}"> {{ dep.dep_name }}</option>
{% endfor %}
</select>
</div>
<br>
<div>
<label>团体:</label>
<select name="group" id="group" multiple="true" >
{% for group in group_list %}
<option value="{{ group.id}}"> {{ group.group_name }}</option>
{% endfor %}
</select>
</div>
<br>
<div><input type="submit" value="增加"></div>
</form>
</div>
</body>
</html>
edit_employee_old函数代码如下:
def edit_employee_old(request,emp_id):
if request.method=="POST":
id=request.POST.get('id')
name=request.POST.get("name")
email=request.POST.get("email")
dep=request.POST.get("dep")
info=request.POST.get("info")
groups=request.POST.getlist("group")
emp=employee.objects.get(id=id)
emp.name=name
emp.email=email
emp.dep_id=dep
emp.info_id=info
emp.group.set(groups)
emp.save()
return redirect('/test_orm_old/list_employee_old/')
emp=employee.objects.get(id=emp_id)
dep_list = department.objects.all()
group_list = group.objects.all()
info_list = employeeinfo.objects.all()
return render(request, 'test_orm_old/edit_employee_old.html',{'emp':emp,'dep_list':dep_list,'group_list':group_list,'info_list':info_list})
edit_employee_old.html如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div align="center">
<h1>修改员工信息</h1>
<hr>
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name='id' id='id' value={{ emp.id }}>
<div>
<label>姓名:</label>
<input type="text" name="name" id="name" value={{ emp.name }}>
</div>
<br>
<div>
<label>邮箱:</label>
<input type="text" name="email" id="email" value={{ emp.email }}>
</div>
<br>
<div>
<label>电话地址:</label>
<select name="info" id="info">
{% for info in info_list %}
{% if emp.info_id == info.id %}
<option value="{{ info.id}}" selected > {{ info.phone }}||{{ info.address }}</option>
{% else %}
<option value="{{ info.id}}"> {{ info.phone }}||{{ info.address }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<br>
<div>
<label>部门:</label>
<select name="dep" id="dep">
{% for dep in dep_list %}
{% if emp.dep_id == dep.id %}
<option value="{{ dep.id}}" selected > {{ dep.dep_name }}</option>
{% else %}
<option value="{{ dep.id}}" > {{ dep.dep_name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<br>
<div>
<label>团体:</label>
<select name="group" id="group" multiple="true" >
{% for group in group_list %}
{% if group in emp.group.all %}
<option value="{{ group.id}}" selected> {{ group.group_name }} </option>
{% else %}
<option value="{{ group.id}}"> {{ group.group_name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<br>
<div><input type="submit" value="保存"></div>
</form>
</div>
</body>
</html>
说明:
{% for dep in dep_list %}
{% if emp.dep_id == dep.id %}
判断id是否一样
Django ORM跨表操作
与外键有关的跨表操作
-
ForeignKey字段
在数据模型中一般把ForeignKey字段设置在“一对多”中“多”的一方,ForeignKey可以和其他表做关联关系,也可以和自身做关联关系。
ForeignKey一般在models文件的数据模型类中定义,其形式如下:
# 员工的部门,外键,形成一对多关系 dep = models.ForeignKey(to="department",to_field='id',related_name="dep_related",on_delete=models.CASCADE)
ForeignKey字段主要有4个属性:
- to用来设置要关联的表。
- to_field用来设置要关联的字段。
- related_name是在反向操作时使用的名字,用于代替原反向查询时的“表名_set”,形如related_name="dep_related",如果这样定义,dep_obj.employee_set.all()就要被dep_obj.dep_related.all()代替。
- on_delete=models.CASCADE用来删除关联数据,与之关联的数据也要删除。
-
外键跨表关联操作
首先介绍数据操作的常规说法,正向操作是指由存在外键的表通过外键查找关联的数据库表,反向操作是由关联表查找存在外键的数据库表。
以前面定义的employee数据表与department数据表为例,正向操作是通过employee的一条记录的外键查找与之关联的department的记录:
emp = employee.objects.get(id=2) dep = emp.dep.dep_name
用emp.dep.dep_name取得员工所在部门的名称,其中emp是保存employee的一条记录对象的变量,dep为外键名字。
而反向操作是通过department的一条记录查找employee中关联的记录,用“表名_set”表示,其中表名用的是含有外键字段的表的名称:
dep_obj = department.objects.get(id=8) emp_list = dep_obj.employee_set.all()
通过dep_obj.employee_set.all()取得一个部门的所有员工名,dep_obj是存储department的一条记录对象的变量,employee_set就是表名_set的形式。
-
外键跨表操作的样例
以一个小例子说明如何通过外键操作关联表。首先在/test_orm/employee/urls.py文件中加入以下语句,建立路径与视图函数的对应关系,这样网址与视图函数就联系在一起了。
path('test_foreign/',test_foreign),
在views中编写test_foreign代码:
def test_foreign(request): # 取出employee的一条记录 emp = employee.objects.get(id=16) # 正向操作,通过外键值dep关联到department数据表的一条记录,然后取得该记录的dep_name字段 dep_name = emp.dep.dep_name dep_obj = department.objects.get(id=6) # 反向操作,通过employee_set关联到employee数据表,然后用all函数取得全部记录 emp_list = dep_obj.employee_set.all() emp_names = [emp.name for emp in emp_list] return HttpResponse("1.正向关联:员工名称:{0},所在部门:{1} <br> 2.反向查找:部门名称:{2},部门员工:{3}".format(emp.name,dep_name,dep_obj.dep_name,emp_names))
-
外键跨表查询字段
查询字段的值也分正向和反向
正向:取得字段值的形式为”外键+双下划线+关联表的字段名”:
emp = models.employee.objects.values_list('name',"dep__dep_name","dep__dep_script") emp2 = models.employee.objects.values('name',"dep__dep_name","dep__dep_script")
values_list()和values()函数传入的参数:name取的是employee数据表中的字段;dep__dep_name是外键+双下划线+关联表的字段名形式,它通过employee外键dep关联到department数据表,然后获取dep_name的值。
values_list()返回的是元组(由字段值组成)列表,values()返回的是字典列表。
反向:取得字段值的形式为”表名+双下划线+字段名”,表名是有外键字段的表的名称:
dep_emp=models.department.objects.values_list("employee__name")
如果外键字段定义了related_name属性,就必须用related_name指定的名字取字段,形式如“related_name值+双下划线+字段名”,举例如下:
# 员工的部门、外键,形成一对多关系,定义了related_name='dep_related' dep = models.ForeignKey(to="department",to_field="id",related_name='dep_related',on_delete=model.CASCADE)
以上外键dep定义了该属性,取字段值用以下代码:
dep_emp=models.department.objects.values_list("dep_related__name","dep_related_email")
与多对多键有关的跨表操作
-
ManyToManyField字段
一般在models中定义,形式如下:
# 员工加入的团体,多对多关系,即一个员工可以加入多个团体,一个团体可以有多个员工 group = models.ManyToManyField(to="group",related_name="group_related")
-
多对多键跨表关联操作
正向操作指的是从有多对多键的表查找关联表,反向操作指的是从关联表查找有多对多键的表。跨表操作主要用函数进行。
create函数:创建一个新的记录并保存在数据库表中,最后将它添加到关联对象集。
# 正向操作 models.employee.objects.first().group.create(group_name="搏击",group_script="搏击也是健身项目") # 反向操作 models.group.objects.first().employee_set.create(name="tom",email='123@1.com',dep_id='11') # 反向操作 models.group.objects.get(id=4).employee_set.create(name="tom",email='123@1.com',dep_id='11')
add函数取出数据库表中的记录然后将其添加到关联数据表的记录集。
group_list = models.group.objects.filter(id__lt=6) models.employee.objects.first().group.add(*group_list)
第一行代码先把group的记录取出来放到group_list变量中,第二行代码把取出的记录通过group.add(*group_list)关联到models.employee.objects.first()取出的记录上,注意变量前要加*。同理可以通过id值进行关联,以下代码是把group中id为1,2,6的记录关联到employee表的第一条记录上:
models.employee.objects.first().group.add(*[1,2,6])
set()函数,更改数据库表中的记录的关联记录,不管记录以前关联任何记录,用新的关联替换。下面代码用group数据表中id为4,5,6的记录关联employee数据表中id为11的记录,注意列表变量前不加*号。
models.employee.objects.get(id=11).group.set([4,5,6])
remove()函数,从记录对象中删除一条关联记录,参数为关联数据库表的id。
obj_list = models.employee.objects.all().first() obj_list.group.remove(4)
clear()函数,从记录对象中删去一切关联记录。以下代码将删去employee数据库表中最后一条记录与group数据库表中关联的一切记录。
models.employee.objects.last().group.clear()
-
多对多关联跨表查询字段值
正向操作查询字段值,取得字段值的形式为多对多键+双下划线+关联表的字段名:
emp_m2m = models.employee.objects.values_list("id","name","group__group_name")
反向操作查询字段值,取得字段值的形式为表名+双下划线+字段名,表名用的是存在多对多键字段的表的名称:
emp_m2m = models.group.objects.values("group_name","employee_name","employee_email")
与一对一键有关的跨表操作
-
OneToOneField字段
一对一的关联关系把原来可以存储在一个表的字段拆开分别设置在两个表中,将查询次数多的字段放在一个表中,将查询次数少的字段放在另一个表中。
class employeeinfo(models.Model): phone = models.CharField(max_length=11) address = models.CharField(max_length=50)
employeeinfo和employee一一对应,employee中有一个字段info,字段类型为OneToOneField:
class employee(models.Model): info = models.OneToOneField(to='employeeinfo',related_name="info_related",on_delete=models.CASCADE)
-
一对一键跨表关联操作
正向操作和反向操作形式与外键基本一样,只是反向操作不用表名_set而直接关联表名:
# 正向操作 emp = models.employee.objects.get(id=1) dep = emp.info.phone emp_info = models.employeeinfo.objects.get(id=2) # 反向操作,因为定义了related_name,所以要用info_related emp_name = emp_info.info_related.name # 反向操作第二种方法,没有定义related_name,不用加_set emp_info = models.employrrinfo.objects.get(id=2) emp_name = emp_info.employee.name
-
一对一关联跨表查询字段值
# 正向 emp_one = models.employee.objects.values("id","name","info__phone","info__address") # 反向 emp_one2 = models.employeeinfo.objects.values("phone","address","employee__name","employee__email")
Django ORM聚合与分组查询
聚合查询
聚合查询主要对“.aggregate()”前面的查询语句取得的数据库表记录进行聚合计算。聚合计算主要有求合计值、求平均值、求最大值、求记录数等,因此aggregate的参数主要是聚合函数Avg()、Sum()、Max()、Min()、Count()等。
在employee数据库表中有一个salary字段:
# 员工数据模型
class employee(models.Model):
salary = models.DecimalField(max_digits=8,decimal_places=2)
如下代码取得salary字段值并求合计值。
from django.db.models import Sum
salary_sum = models.employee.objects.filter(id__lt=8).aggregates(Sum("salary"))
说明:
-
首先导入与聚合函数相关的模块。
-
首先查询id值小于8的的记录然后通过聚合函数sum把查询到的所有记录的salary字段值加在一起
-
聚合查询返回一个包含一些键值对的字典,返回值形式如下,返回值键名为字段名+双下划线+聚合函数。
{'salary__sum':Decimal('89879.9')}
下面代码为聚合查询返回的字典的键指定一个名称,返回值为{'salary_hj':Decimal('89879.9')}.
salary_sum = models.employee.objects.filter(id__lt=18).aggregate(salary_hj=Sum("salary"))
如果你希望生成不止一个聚合查询值,可以向aggregate函数中添加多个聚合函数:
from django.db.models import Sum,Avg,Max,Min,Count salary_data = models.employee.objects.filter(id__lt=18).aggregate(count=Count("id"),salary_hj=Sum("salary"),salary_pj=Avg("salary"),salary)zd=Max("salary"),salary_zx=Min("salary")
返回值也是字典类型:
{'count:6','salary_hj':Decimal('89087.0'),......}
分组查询
-
查询语句不含values函数
以下是分组查询的一个样例,其统计每个员工参加的团体的个数。
emp_list = models.employee.objects.annotate(groupnum=Count("group")) for emp in emp_list: print(emp.name,':参加',emp.groupnum,'个团体')
统计每一个部门薪水最高值:
dep_list = models.department.objects.annotate(maxsalary=Max("employee__salary")) for dep in dep_list: print(dep.dep_name,dep.maxsalary)
employee__salary通过双下划线取得关联表的字段值。
以上代码还有另一种实现方式,可以采用values_list()函数:
dep_list = models.department.objects.annotate(maxsalary=Max("employee__salary")).values_list("dep_name","maxsalary") for dep in dep_list: print(dep)
-
查询语句包含values()函数
下面代码中"values('dep')"起的作用是以dep值分组字段,相当于sql语句的group by dep。代码实现的功能就是计算每个部门员工的平均工资。
dep_salary = models.employee.objects.values('dep').annotate(avg=Avg("salary")).values('dep__dep_name',"avg")
Django ORM中的F和Q函数
F函数:在Django ORM查询语句中,要实现字段值与字段值的比较或运算操作等就要用到F函数,在F函数中传入字段名就能取得字段的值。
以下实现id值小于30的员工的薪水增加600的功能。
from django.db.models import F
models.employee.objects.filter(id__lt=30).update(salary=F("salary")+600)
Q函数:filter等函数中传入的条件参数是“与”关系,相当于sql中的AND。通过把条件参数传给Q函数,再把各个Q函数与”&”“|”“~”操作符进行组合生成复杂的查询条件。
# 在employee数据表中查询id小于30或者salary小于1000的记录。
from django.db.models import Q
obj = models.employee.objects.filter(Q(id__lt=30)|Q(salary__lt=1000))
# employee数据表中查询salary大于1000并且name字段值开头不是李的记录。
obj = models.employee.objects.filter(Q(salary__gt=1000)|~Q(name__startswith='李'))