1. 字段
1.1 自定义无符号整数
class UnsignedIntegerField(models.IntegerField):
def db_type(self, connection):
return 'integer UNSIGNED'
PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
1.2 Model 验证和错误提示
Model 中验证和错误提示有两种方式:
- Admin 内部的
ModelForm
:优先 - 验证 Model 字段:上述验证通过,才验证
- 通过
ModelForm
验证:
# admin.py
from django.contrib import admin
from app01 import models
from django import forms
class UserInfoForm(forms.ModelForm):
age = forms.IntegerField(initial=1, error_messages={'required': '请输入数值', 'invalid': '年龄必须为数值'})
class Meta:
model = models.UserInfo
fields = '__all__'
exclude = ['title']
labels = {'name': 'Writer',}
help_texts = {'name': 'some useful help text',} # 提示信息
error_messages = {'name': {'max_length': 'this writer name is too long'}} # 错误类型,字典
# widgets = {'name': Textarea(attrs={'cols': 80, 'rows': 20})}
class UserInfoAdmin(admin.ModelAdmin):
form = UserInfoForm
admin.site.register(models.UserInfo, UserInfoAdmin)
- Model 字段验证:
# models.py
from django.db import models
class UserInfo(models.Model):
nid = models.AutoField(primary_key=True)
username = models.CharField(max_length=32)
email = models.EmailField(error_messages={'invalid': '格式错误'})
def __str__(self):
return self.username
# views.py
from app01 import models
from django.shortcuts import render, HttpResponse
def index(request):
obj = models.UserInfo(username='12', email='12')
try:
print(obj.clean_fields()) # Model 的 clean 方法是一个钩子函数,可用于定制操作
except Exception as e:
print(e) # {'email': ['格式错误']}
return HttpResponse('ok')
# 访问 http://127.0.0.1:8000/app01/index/
3. 多表关系及参数
- 一对一:Foreignkey 基础上加一个唯一索引 unique
- 一对多:ForeignKey
- 多对多:ManyToMany(两个一对多,两个 ForeignKey 相连)
3.1 一对多模型
一对多模型,如:员工部门表,一个部门可以有多个员工。那么 Django 怎么建立这种关系呢?
其实就是通过外键 ForeignKey
进行关联,在多的一方,字段指定外键即可。
ForeignKey 字段参数
ForeignKey(ForeignObject) # ForeignObject(RelatedField)
to, # 要进行关联的表名
to_field=None, # 要关联的表中的字段名称
on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为
- models.CASCADE,删除关联数据,与之关联也删除
- models.DO_NOTHING,删除关联数据,引发错误IntegrityError
- models.PROTECT,删除关联数据,引发错误ProtectedError
- models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
- models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
- models.SET,删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
def func():
return 10
class MyModel(models.Model):
user = models.ForeignKey(
to="User", # 关联 User 表
to_field="id" # 关联 User 表 的 id 字段
on_delete=models.SET(func),)
related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件:
# 如:
- limit_choices_to={'nid__gt': 5}
- limit_choices_to=lambda : {'nid__gt': 5}
from django.db.models import Q
- limit_choices_to=Q(nid__gt=10)
- limit_choices_to=Q(nid=8) | Q(nid__gt=10)
- limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
db_constraint=True # 是否在数据库中创建外键约束
parent_link=False # 在Admin中是否显示关联数据
示例:
class Department(models.Model):
"""部门表"""
name = models.CharField(max_length=32)
create_data = models.DateField(auto_now_add=True) # 创建时间
id_delete = models.BooleanField(default=False) # 是否可删除
class Meta:
db_table = 'department'
class Employee(models.Model):
"""员工表"""
name = models.CharField(max_length=32)
age = models.IntegerField()
gender = models.IntegerField(default=0)
salary = models.DecimalField(max_digits=8, decimal_places=2) # max_digits 表示八个数字,包括两位小数,decimal_places 表示保留两位小数
# null=True 表示可以为空, blank=True 表示 Django admin 后台可以输入空
comment = models.CharField(max_length=300, null=True, blank=True)
hire_data = models.DateField(auto_now_add=True)
department = models.ForeignKey('Department') # 外键字段
class Meta:
db_table = 'employee' # 数据表名字
Tips
在设置外键时,需要给子表(有外键的表)指定当主表(被连接的表)删除数据时,从表该如何处理。Django 通过设置 on_delete
属性来控制,它有三种值:
- 删除时报错:
DO_NOTHING/PROTECT
- 级联删除:主表删除数据,从表与之关联的数据也将被删除:
CASCADE
- 设置默认值:
SET_NULL/SET_DEFAULT
,set_null
仅在字段可以是 null 时才能使用
3.2 一对一模型
OneToOneField(ForeignKey)
to, # 要进行关联的表名
to_field=None # 要关联的表中的字段名称
on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为
###### 对于一对一 ######
# 1. 一对一其实就是 一对多 + 唯一索引
# 2.当两个类之间有继承关系时,默认会创建一个一对一字段
# 如下会在A表中额外增加一个c_ptr_id列且唯一:
class C(models.Model):
nid = models.AutoField(primary_key=True)
part = models.CharField(max_length=12)
class A(C):
id = models.AutoField(primary_key=True)
code = models.CharField(max_length=1)
# 使用 OneToOneField 字段创建一对一模型
class Person(models.Model):
name = models.CharField(max_length=32)
o2o = models.OneToOneField(to='Person_detail', to_field='id', on_delete=models.CASCADE)
class Person_detal(models.Model):
age = models.IntegerField()
gender_choices = [(0, '男'), (1, '女')]
gender = models.IntegerField(
choices=gender_choices,
default=0,
)
height = models.PositiveIntegerField() # 正整数
email = models.EmailField(max_length=64)
3.3 多对多模型
多对多其实就是两个一对多,通过两个外键将三张表相连,其中第三张表存储了前面两张表的对应关系。例如:名字和爱好表,一个人可以有多个爱好,一个爱好也可以是多个人有。
如何创建三张表:
- 自动创建:
ManyToMangField
字段自动创建,不能新增列 - 手动创建:元
Meta
,可以新增列 - 手动加自动:元
Meta
+ManyToMangField
多对多 ManyToManyField
字段参数:
ManyToManyField(RelatedField)
to, # 要进行关联的表名
related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件:
# 如:
- limit_choices_to={'nid__gt': 5}
- limit_choices_to=lambda : {'nid__gt': 5}
from django.db.models import Q
- limit_choices_to=Q(nid__gt=10)
- limit_choices_to=Q(nid=8) | Q(nid__gt=10)
- limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
# 做如下操作时,不同的symmetrical会有不同的可选字段
models.BB.objects.filter(...)
# 可选字段有:code, id, m1
class BB(models.Model):
code = models.CharField(max_length=12)
m1 = models.ManyToManyField('self',symmetrical=True)
# 可选字段有: bb, code, id, m1
class BB(models.Model):
code = models.CharField(max_length=12)
m1 = models.ManyToManyField('self',symmetrical=False)
through=None, # 自定义第三张表时,使用字段用于指定关系表
through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
)
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)
db_constraint=True, # 是否在数据库中创建外键约束
db_table=None, # 默认创建第三张表时,数据库中表的名称
自动创建
class UserInfo(models.Model):
username = models.CharField(max_length=32, verbose_name='用户名')
m = models.ManyToManyField(
to = 'Tag',
related_name = 'bb',
)
class Tag(models.Model):
title = models.CharField(max_length=32)
手动创建
class UserInfo(models.Model):
username = models.CharField(max_length=32, verbose_name='用户名')
class Tag(models.Model):
title = models.CharField(max_length=32)
# 手动创建第三张表
class UserToTag(models.Model):
tid = models.AutoField(primary_key=True)
u = models.ForeignKey('UserInfo', on_delete=models.CASCADE) # 通过外键相连
t = models.ForeignKey('Tag', on_delete=models.CASCADE)
# 联合唯一索引
class Meta:
unique_together = [
('u', 't'),
]
手动加自动
class UserInfo(models.Model):
username = models.CharField(max_length=32, verbose_name='用户名')
m = models.ManyToManyField(
through = 'UserToTag', # 指定第三张表,不自动创建
through_fields = ['u', 't'] # 指定第三张表的字段
)
class Tag(models.Model):
title = models.CharField(max_length=32)
# 手动创建第三张表
class UserToTag(models.Model):
tid = models.AutoField(primary_key=True)
u = models.ForeignKey('UserInfo', on_delete=models.CASCADE) # 通过外键相连
t = models.ForeignKey('Tag', on_delete=models.CASCADE)
# 联合唯一索引
class Meta:
unique_together = [
('u', 't'),
]
3.4 自关联模型
自关联模型,即表中的某列关联表中另一列。最典型的自关联模型就是地区表(省市县三级联动)省的 pid 为 null,市的 pid 为省的 id,县的 pid 为市的 id。具体如下:
自关联表现形式
- 省的
parent_id
为 null - 市的
parent_id
为对应省的id
- 区的
parent_id
为对应市的id
- 数据库设计
models.py
class Area(models.Model):
name = models.CharField(max_length=32, verbose_name='名称')
parent = models.ForeignKey('self', # 自关联字段的外键指向自身,也可以是 Area
verbose_name='上级行政区划',
on_delete=models.SET_NULL,
related_name='subs',
null=True,
blank=True
)
class Meta:
db_table = 'tb_areas' # 自定义表名
verbose_name = '行政区划' # admin 中显示的名称
verbose_name_plural = '行政区划'
def __str__(self):
return self.name
自关联模型,最核心的地方就是自己关联自己 self/Area
,用一张表做两张表才能做的事。
- 路由系统
app/urls.py
from django.urls import path
from app import views
urlpatterns = [
path('area/', views.area, name='area'),
path('getPro/', views.getPro, name='getArea'),
path('getCity/', views.getCity, name='getCity'),
path('getDis/', views.getDis, name='getDis'),
]
- 视图函数
views.py
from django.shortcuts import render, HttpResponse
from app import models
from django.http import JsonResponse
# 访问 http://127.0.0.1:8000/app/area/,返回 area.html
def area(request):
return render(request, 'area.html')
def getPro(request):
"""获取省份信息"""
pro_list = models.Area.objects.filter(parent_id=None) # 省份的 parent_id 为 None
res = []
for i in pro_list:
res.append([i.id, i.name])
print(res) # [[1, '广东省'], [7, '湖南省']]
return JsonResponse({'pro_list': res}) # JsonResponse 打包成 json 格式字符串
def getCity(request):
"""获取市信息"""
city_id = request.GET.get('city_id')
city_list = models.Area.objects.filter(parent_id=int(city_id))
res = []
for i in city_list:
res.append([i.id, i.name])
print('city', res) # [[2, '深圳市'], [3, '广州市'], [6, '湛江市']]
return JsonResponse({'city_list': res})
def getDis(request):
"""获取区信息"""
dis_id = request.GET.get('dis_id')
dis_list = models.Area.objects.filter(parent_id=int(dis_id))
res = []
for i in dis_list:
res.append([i.id, i.name])
return JsonResponse({'dis_list': res})
- 模板
area.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<select id="pro">
<option value="">请选择省份</option>
</select>
<select id="city">
<option value="">请选择城市</option>
</select>
<select id="dis">
<option value="">请选择区</option>
</select>
<script src="{% static 'jquery-3.1.1.js' %}"></script>
<script>
$(function () {
var pro = $('#pro');
var city = $('#city');
var dis = $('#dis');
// 查询省信息,$.get() 为 $.ajax 的简化版
// 向 /app/getPro/ 发送 ajax 请求,arg 为请求成功返回的信息
$.get('/app/getPro/', function (arg) {
console.log(arg); // {'pro_list': [[1, '广东省'],[7, '湖南省']]}
$.each(arg.pro_list, function (index, item) {
console.log(item); // [1, '广东省'] 、[7, '湖南省']
console.log(index); // 0、1
// 将从数据库中取出的数据添加到 option 标签中,其中 value 为 id,文本值为 name
var $new = $("<option value="+ item[0] +">" + item[1] + "</option>");
pro.append($new);
});
{# 或者使用 JavaScript for 循环#}
{# console.log(arg.pro_list);#}
{# for (var i = 0, len=arg.pro_list.length; i<len; i++){#}
{# var $new = $('<option value='+ arg.pro_list[i][0] +'>' + arg.pro_list[i][1] + '</option>')#}
{# pro.append($new);#}
{# }#}
});
// 根据省的变化查询市,改变省时,清空市和区
pro.change(function () {
city.empty().append('<option value="">请选择市</option>');
dis.empty().append('<option value="">请选择市</option>');
$.ajax({
url: '/app/getCity/',
type: 'get',
data: {'city_id': $(this).val()},
success: function (arg) {
$.each(arg.city_list, function (index, item) {
var $new = $('<option value='+ item[0]+'>' + item[1] + '</value>');
city.append($new);
})
}
})
});
// 根据市变化查询区,改变市,清空区
city.change(function () {
dis.empty().append('<option value="">请选择市</option>');
$.ajax({
url: '/app/getDis/',
type: 'get',
data: {'dis_id': $(this).val()},
success: function (arg) {
console.log('区', arg.dis_list); // [[4, '宝安区'], ]
$.each(arg.dis_list, function (index, item) {
console.log(item[1]);
var $new = $('<option value='+ item[0] +'>' + item[1] + '</value>');
dis.append($new);
})
}
})
})
})
</script>
</body>
</html>
当访问 http://127.0.0.1:8000/app/area/
时,会向 /app/getPro/
发送 ajax 请求,请求成功获得包含省份 id、name 的信息 {'pro_list': [[1, '广东省'],[7, '湖南省']]}
。取出数据遍历循环,添加到 option
标签中,从而获得省份信息。
当我们改变省份时,将上一次省份的市、区信息清空,避免添加重复。获取当前省份的 id,并向 /app/getDis/
发送 ajax 请求,从而获得相应市的信息,同理区也是一样。
- 添加数据
views.py
添加数据,可以用 pycharm 自带的数据库添加,也可以手动添加:
def add(request):
models.Area.objects.create(id=7, name='湖南省', parent_id=None)
models.Area.objects.create(id=8, name='长沙市', parent_id=7)
models.Area.objects.create(id=9, name='雨花区', parent_id=8)
return HttpResponse('添加成功')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wdJ5AtQO-1679749350500)(https://raw.githubusercontent.com/hj1933/img/master/iimg/%E7%BA%A7%E8%81%94%E8%8F%9C%E5%8D%95.gif)]
参考博客:
3.5 知识点补充
Ajax 的 get() 方法
,它可以向后台发送 ajax 请求,请求成功可调用回调函数。如果需要在出错时执行函数,请使用 $.ajax
,它是 $.ajax
简化版本。
语法
$(selector).get(url, data, success(response, status, xhr), dataType); // 除了 url 必需,其他都可选,status 为请求状态,xhr - 包含 XMLHttpRequest 对象
$('button').click(function(){
get('/app/getdata/', function(arg){});
});
3.6 Model 常用设计模型示例及方法
3.6.1 Model 设计
总共四张表,分别是班级、学生、学生个人信息以及老师表,之间的关系如下:
class Class(models.Model):
"""班级表"""
class_name = models.CharField(max_length=32)
create_data = models.DateField()
def __str__(self):
return self.class_name
class Meta:
verbose_name = '班级'
class Student(models.Model):
"""
学生表
一每个学生都有对应的个人信息(一对一),一个班级可以有多个学生(一对多)
"""
student_name = models.CharField(max_length=32)
# 一对多,与班级关联
sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)
# 一对一,与学生个人信息关联
detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)
class Meta:
verbose_name = '学生'
def __str__(self):
return self.student_name
class StudentDetail(models.Model):
"""学生个人信息表"""
age = models.IntegerField()
gender_choices = [(0, '男'), (1, '女')]
gender = models.IntegerField(
choices=gender_choices,
default=0,
)
height = models.PositiveIntegerField() # 正整数
email = models.EmailField(max_length=64)
class Teacher(models.Model):
"""
老师表
一个老师可以教多个班,一个班也可以有多个老师
"""
teacher_name = models.CharField(max_length=32)
tc = models.ManyToManyField(to='Class', related_name='b')
class Meta:
verbose_name = '老师'
def __str__(self):
return self.teacher_name
- 老师表
app_teacher
:
- 学生个人信息表
app_student_detail
:
- 学生表
app_student
:
- 班级表
app_class
:
- 老师、班级关系表(第三张表)
app_teacher_tc
:
3.6.2 查询操作
def query(request):
########### 一对一 #####################
# 正向查询(根据外键字段)
# 根据学生名字查询其个人信息
# obj = models.Student.objects.filter(student_name='rose')[0]
# print(obj)
# print(obj.detail.age, obj.detail.gender, obj.detail.height, obj.detail.email)
# # 17 1 170 456@qq.com
# 反向查询(根据要查询的数据表名)
# 根据邮箱查询学生名字
# obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0]
# print(obj2) # <QuerySet [<StudentDetail: StudentDetail object (2)>]>
#
# print(obj2.student.student_name) # rose
############## 一对多(班级表和学生表) ##################
# 正向查询
# 根据学生名查询所属班级
# obj3 = models.Student.objects.get(student_name='rose')
# print(obj3.sc_id) # 1
# print(type(obj3.sc)) # <class 'app.models.Class'>
# print(obj3.sc.class_name) # 一班
# 反向查询
# 二班有哪些学生
# obj4 = models.Class.objects.filter(class_name='二班')[0]
# print(obj4)
# # res = obj4.student_set.all() # 如果外键字段没有设置 related_name 就用 表名_set
# res = obj4.stu.all()
# print(res) # <QuerySet [<Student: john>, <Student: lila>]>
# for i in res:
# print(i.student_name) # john、lila
# 方法二
# ret = models.Student.objects.filter(sc=obj4).values('student_name') # 字典形式
# print(ret) # <QuerySet [{'student_name': 'john'}, {'student_name': 'lila'}]>
# for i in ret:
# print(i['student_name']) # john、lila
# # 双下划线
# 正向
obj4 = models.Class.objects.filter(class_name='二班').values('stu__student_name')
# 反向
obj5 = models.Student.objects.filter(sc__class_name='二班').values('student_name')
############################### 多对多(老师表与班级表) ############################################
# 查看 孙老师教哪几个班
# 正向查询(通过多对多字段)
# obj5 = models.Teacher.objects.filter(teacher_name='孙老师')[0]
# ret = obj5.tc.all()
# print(ret) # <QuerySet [<Class: 一班>, <Class: 二班>]>
# for i in ret:
# print(i.class_name)
# 查看一班有哪几个老师
# 反向查询
# obj5 = models.Class.objects.filter(class_name='一班')[0]
# ret = obj5.b.all()
# print(ret) # < QuerySet[ < Teacher: 孙老师 >, < Teacher: 刘老师 >] >
# for i in ret:
# print(i.teacher_name) # 孙老师、刘老师
# # 双下划线
# # 如果没有设置 related_name = b,那么就是 values('teacher__name`) 即要查的表的表名__要查询的字段
# obj6 = models.Class.objects.filter(class_name='一班').values('b__teacher_name')
# print(obj6) # <QuerySet [{'b__teacher_name': '孙老师'}, {'b__teacher_name': '刘老师'}]>
# # 正向 tc = models.ManyToManyField(to='Class', related_name='b')
# obj6 = models.Teacher.objects.filter(tc__class_name='一班').values('teacher_name')
return HttpResponse('查询成功!')
3.6.3 总结
一对一、一对多、多对多,最好都设置好 related_name
参数,没有设置时,反向查找用 要查询的命名.查询字段 或 _set.all()。
一对一
- 正向查询:根据
OneToOneField
字段查询 - 反向查询:根据 要查询的命名.查询字段查询
# detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)
# 正向
obj = models.Student.objects.filter(student_name='rose')[0]
print(obj.detail.age)
# 反向
obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0]
print(obj2.student.student_name) # 这里没设置 related_name 因此用表名
一对多
- 正向查询:根据外键字段查询
- 反向查询
- 设置了
related_name
,就用这个名字查询 - 没有设置,用表名_set 方式查询
- 设置了
- 双下划线查询
- 正向:
filter(条件).values(要查询的表名__要查询字段)
- 反向:
filter(外键字段__条件).values(要查询字段)
- 正向:
# sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)
# 正向
obj3 = models.Student.objects.get(student_name='rose')
print(obj3.sc.class_name) # 根据外键字段 sc 查询
# 反向
obj4 = models.Class.objects.filter(class_name='二班')[0]
# res = obj4.student_set.all() # 没设置 related_name,用表名_set
res = obj4.stu.all() # 用 related_name 名字
# 双下划线
# 正向
obj5 = models.Class.objects.filter(class_name='二班').values('stu__student_name')
# 反向
obj6 = models.Student.objects.filter(sc__class_name='二班').values('student_name')
多对多
- 正向查询:
ManyToManyField
字段查询 - 反向查询:表名_set、
related_name
名字查询 - 双下划线与一对多一样
# tc = models.ManyToManyField(to='Class', related_name='b')
# 正向
obj5 = models.Teacher.objects.filter(teacher_name='孙老师')[0]
print(obj5.tc.all())
# 反向
obj5 = models.Class.objects.filter(class_name='一班')[0]
print(obj5.b.all())
参考博客:django 一对一、一对多、多对多操作、常用方法
4. ORM 操作
4.1 基本操作
添加数据
# 方法一
models.Tb.objects.create(id=1, name='rose') # 增加一条数据,可接受字典类型数据 **kwargs
# 方法二
obj = models.Tb(id=1, name='rose')
obj.save()
查询数据
models.Tb.objects.get(id=123) # 获取单条数据,不存在报错(不建议)
models.Tb.objects.all() # 获取全部,QuerySet 集合
obj = models.Tb.objects.filter(name='rose') # 获取指定条件数据,QuerySet 集合,取第一个 obj[0]
models.Tb.objects.exclude(name='rose') # 排除指定条件数据
删除数据
models.Tb.objects.filter(name='rose').delete()
修改数据
models.Tb.objects.filter(name='rose').update(age=19)
# 方法二
obj = models.Tb.objects.get(id=1)
obj.age = 19
obj.save()
4.2 进阶操作
以下为 QuerySet 对象方法,因此只有 all()、filter()、exclude()
方法才有:
# 获取个数
models.Tb.objects.filter(name='rose').count()
# 大于、小于
models.Tb1.objects.filter(id__gt=1) # 获取id大于1的值
models.Tb1.objects.filter(id__gte=1) # 获取id大于等于1的值
models.Tb1.objects.filter(id__lt=10) # 获取id小于10的值
models.Tb1.objects.filter(id__lte=10) # 获取id小于10的值
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值
# in
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in
# isnull
models.Tb1.objects.filter(pub_date__isnull=True)
# contains
models.Tb1.objects.filter(name__contains="ven")
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
models.Tb1.objects.exclude(name__icontains="ven")
# range
models.Tb1.objects.filter(id__range=[1, 2]) # 范围 bettwen and
# 其他类似,以什么开头、结尾,大小写不敏感等
models.Tb1.objects.filter(name__startswith='r')
# 排序 order by
models.Tb1.objects.filter(name='seven').order_by('id') # asc
models.Tb1.objects.filter(name='seven').order_by('-id') # desc 降序
############################## 聚合查询和分组查询 ###########################################
# 分组 group by
# annotate() 可以为 QuerySet 中每个对象生成一个独立的摘要,输出结果仍然是一个 QuerySet 对象,能调用 filter()、order by() 甚至是 annotate()
from django.db.models import Count, Min, Max, Sum, Avg
# 分组条件为 id
obj = models.Student.objects.filter(student_name='rose').values('id').annotate(c=Count('sc_id'))
print(obj) # <QuerySet [{'id': 1, 'c': 1}]>
# obj = models.Student.objects.annotate(Count('sc_id'))
# print(obj) # <QuerySet [<Student: rose>, <Student: john>, <Student: tom>, <Student: lila>, <Student: david>]>
# print(obj[0].student_name) # rose
# 执行的 SQL 语句
SELECT "app_student"."id", COUNT("app_student"."sc_id") AS "c" FROM "app_student" WHERE "app_student"."student_name" = 'rose' GROUP BY "app_student"."id" LIMIT 21; args=('rose',)
# aggregate() 用于 QuerySet 对象汇总值,返回一个聚合值得字典
obj1 = models.Student.objects.all().aggregate(id_avg=Avg('sc_id'))
print(obj1) # 没取别名:{'sc_id__avg': 1.8} {'id_avg': 1.8}
# models.Student.objects.aggregate(Avg('sc_id'))
# 相当于
SELECT AVG("app_student"."sc_id") AS "id_avg" FROM "app_student"; args=()
##################################################################################################
# limit、offset 限制
obj = models.Student.objects.all()[1:3] # 索引:[1,3)
# regex 正则匹配,iregex 不区分大小写,有时候 Django 查询 API 不能方便设置查询条件,我们可以自己定制
obj = models.Student.objects.filter(student_name__regex=r'^r|t') # 以 r、t开头
# 日期:date、year、month、day、weekday、hour、minute、second
# create_date = models.DateTimeField()
models.Tb1.objects.filter(create_date__date=datetime.date(2019, 2, 15))
models.Tb1.objects.filter(create_date__date__gt=datetime.date(2005, 1, 1))
models.Tb1.objects.filter(create_date__year=2019) # 过滤条件 create_date 等于 2019 年
models.Tb1.objects.filter(create_date__year__gte=2019) # 大于等于
# 其余的月、日、时间等用法类似
# F、Q
# F 在原来基础上进行操作
obj = models.StudentDetail.objects.all().update(age=F('age')+1) # 所有用户年龄加 1 岁
print(obj) # obj: 5 影响的条数
# 原生SQL: UPDATE "app_studentdetail" SET "age" = ("app_studentdetail"."age" + 1);
# Q 构建搜索条件(在这里解决了或、与的问题)
# 1、 Q 对象可以对关键字参数进行封装,从而更好地应用多个查询
obj1 = models.StudentDetail.objects.filter(Q(age=18)) # 构建过滤条件
# 2、组合使用 &、| 、~ 操作符
obj2 = models.StudentDetail.objects.filter(Q(age=18) | Q(age=19)) # 解决了 Django API 中没有或的问题
obj3 = models.StudentDetail.objects.filter(Q(age=18) & Q(age=19)) # 解决了 Django API 中没有与的问题
obj3 = models.StudentDetail.objects.filter(~ Q(age=19)) # 解决了 Django API 中没有非的问题
# 方法二:手动创建逻辑关系
q1 = Q() # 创建 Q 对象
q1.connector = 'OR' # q1 内部为 or 关系,即 age=18 或 age =19
q1.children.append(('age', 18))
q1.children.append(('age', 19))
q2 = Q() # 创建 Q 对象
q2.connector = 'AND' # q2 内部为 and 关系
q2.children.append(('gender', 0))
q3 = Q() # 创建 Q 对象
q3.add(q1, 'AND') # 将 q1、q2 都添加到 q3 中,创建总的过滤条件
q3.add(q2, 'AND')
obj = models.StudentDetail.objects.filter(q3)
print(obj) # <QuerySet [<StudentDetail: StudentDetail object (1)>]>
# 原生 SQL
SELECT "app_studentdetail"."id", "app_studentdetail"."age", "app_studentdetail"."gender", "app_studentdetail"."height", "app_studentdetail"."email" FROM "app_studentdetail" WHERE (("app_studentdetail"."age" = 18 OR "app_studentdetail"."age" = 19) AND "app_studentdetail"."gender" = 0) LIMIT 21;
# 3、Q 对象 可以和关键字参数一起查询使用,不管一定要在关键字参数前面
models.StudentDetail.filter(Q(age=18), email__startswith='123')
# 4、应用
import datetime
obj = models.Class.objects.filter(Q(create_data=datetime.date(2019,2,15)))
print(obj)
4.3 公共方法
用于查询数据,并返回一个 QuerySet 对象集合。
def all(self)
# 获取所有的数据对象
def filter(self, *args, **kwargs)
# 条件查询
# 条件可以是:参数,字典,Q
def exclude(self, *args, **kwargs)
# 条件查询
# 条件可以是:参数,字典,Q
def select_related(self, *fields)
性能相关:表之间进行join连表操作,一次性获取关联的数据。
model.tb.objects.all().select_related()
model.tb.objects.all().select_related('外键字段')
model.tb.objects.all().select_related('外键字段__外键字段')
def prefetch_related(self, *lookups)
性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
# 获取所有用户表
# 获取用户类型表where id in (用户表中的查到的所有用户ID)
models.UserInfo.objects.prefetch_related('外键字段')
from django.db.models import Count, Case, When, IntegerField
Article.objects.annotate(
numviews=Count(Case(
When(readership__what_time__lt=treshold, then=1),
output_field=CharField(),
))
)
students = Student.objects.all().annotate(num_excused_absences=models.Sum(
models.Case(
models.When(absence__type='Excused', then=1),
default=0,
output_field=models.IntegerField()
)))
def annotate(self, *args, **kwargs)
# 用于实现聚合group by查询,values() 是谁,就对谁 group by
from django.db.models import Count, Avg, Max, Min, Sum
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
def distinct(self, *field_names)
# 用于distinct去重
models.UserInfo.objects.values('nid').distinct() # 对 nid 去重
# select distinct nid from userinfo
注:只有在PostgreSQL中才能使用distinct进行去重
def order_by(self, *field_names)
# 用于排序,reverse() 只对 order by 后的结果反转,其他无效
models.UserInfo.objects.all().order_by('-id','age')
models.UserInfo.objects.all().order_by('-id','age').reverse()
def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
# 构造额外的查询条件或者映射,如:子查询,自定义 SQL 语句
Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
# 原生 SQL:
# select id, (select col from sometable where othercol > %s ) as new_id from Entry
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) # 逗号为 and,相当于 (foo='a' or bar = 'a') and baz = 'a'
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
def reverse(self):
# 倒序
models.UserInfo.objects.all().order_by('-nid').reverse()
# 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序
def defer(self, *fields):
# 映射中排除某列数据,若有 20列,取 18列,不需要全写出来,可以排除剩余两列
models.UserInfo.objects.defer('username','id')
或
models.UserInfo.objects.filter(...).defer('username','id')
def only(self, *fields):
#仅取某个表中的某几列,返回的还是 QuerySet 对象
models.UserInfo.objects.only('username','id')
或
models.UserInfo.objects.filter(...).only('username','id')
# select id, username from userinfo where ...
def using(self, alias):
# 指定使用的数据库,参数为别名(setting中的设置),实质是用来连接别的数据库
DATABASES = {
'default2': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
models.Tb1.objects.all().order_by('id', 'name').using('default2')
##################################################
# 返回 QuerySet 子类 #
##################################################
def raw(self, raw_query, params=None, translations=None, using=None):
# 执行原生SQL
models.UserInfo.objects.raw('select * from userinfo')
# 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
models.UserInfo.objects.raw('select id as nid from 其他表')
# 为原生SQL设置参数
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
# 将获取的到列名转换为指定列名
name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
# 指定数据库
models.UserInfo.objects.raw('select * from userinfo', using="default")
################### 原生SQL ###################
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone() # fetchall()/fetchmany(..)
def values(self, *fields):
# 获取每行数据为字典格式
def values_list(self, *fields, **kwargs):
# 获取每行数据为元祖
def dates(self, field_name, kind, order='ASC'):
# 根据时间进行某一部分进行去重查找并截取指定内容
# kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
# order只能是:"ASC" "DESC"
# 并获取转换后的时间
- year : 年-01-01
- month: 年-月-01
- day : 年-月-日
models.DatePlus.objects.dates('ctime','day','DESC')
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
# 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
# kind只能是 "year", "month", "day", "hour", "minute", "second"
# order只能是:"ASC" "DESC"
# tzinfo时区对象
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))
"""
pip3 install pytz
import pytz
pytz.all_timezones
pytz.timezone(‘Asia/Shanghai’)
"""
def none(self):
# 空QuerySet对象
####################################
# 数据库查询 #
####################################
def aggregate(self, *args, **kwargs):
# 聚合函数,获取字典类型聚合结果
from django.db.models import Count, Avg, Max, Min, Sum
result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
===> {'k': 3, 'n': 4}
def count(self):
# 获取个数
def get(self, *args, **kwargs):
# 获取单个对象
def create(self, **kwargs):
# 创建对象
def bulk_create(self, objs, batch_size=None):
# 批量插入
# batch_size表示一次插入的个数
objs = [
models.DDD(name='r11'),
models.DDD(name='r22')
]
models.DDD.objects.bulk_create(objs, 10)
def get_or_create(self, defaults=None, **kwargs):
# 如果存在,则获取,否则,创建
# defaults 指定创建时,其他字段的值
obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})
def update_or_create(self, defaults=None, **kwargs):
# 如果存在,则更新,否则,创建
# defaults 指定创建时或更新时的其他字段
obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})
def first(self):
# 获取第一个
def last(self):
# 获取最后一个
def in_bulk(self, id_list=None):
# 根据主键ID进行查找
id_list = [11,21,31]
models.DDD.objects.in_bulk(id_list)
def delete(self):
# 删除
def update(self, **kwargs):
# 更新
def exists(self):
# 是否有结果
性能相关
数据库操作越少,程序运行性能越高,因此我们要尽可能地减少对数据库的操作。
select_related(self, *fields)
方法用于连接数据表:
# models.py
class Department(models.Model):
name = models.CharField(max_length=32)
class Employee(models.Model):
name = models.CharField(max_length=32)
fk = models.ForeignKey('Department')
# views.py
obj_list = models.Employee.objects.all() # select * from employee
for item in obj_list:
print(item.name)
print(item.fk.name) # 跨表查询部门表的名字
上面当在跨表进行查询时还会对 department 查询一次,总共要查询两次。连表查询:
# 加上 select_related() 后,只需一次
obj_list = models.Employee.objects.all().select_related('fk') # 外键字段
# 相当于 select * from employee left join department on employee.fk_id = department.id
Tips
- 没有跨表查询,没必要连表
- 支持多张表连接:
select_related('fk', 'fk2')