1. ORM 简介
MTV 设计模式中,模型(M)就是对数据库的操作,在 Web 开发中,使用最频繁的也是对数据库的操作,那么该怎么样去实现呢?
我们不可能自己手动去写大量的 SQL 语句,因为我们也不是专业 DBA 人员,那么我们就只能奢求有一种能够使用 Python 程序对数据库操作的方法了。这就是 ORM(Object Relation Mapping)对象关系映射,以面向对象的方式去操作数据库。
其实现模式大致是这样的:
Django 本身给我们提供了强大的 ORM 系统,不需要再额外的
安装,当然还有一些其他的 ORM ,如:SQLAlch 等。
2. 字段
Model 中的字段 fileds
即数据表中的列,用来存储数据(记录),字段类型对应列的数据类型。
2.1 常用字段类型
Django 内置了很多字段类型,都位于 django.db.models
中,以下为常用字段类型:
AutoField(Field)
- int自增列,必须填入参数 primary_key=True
BigAutoField(AutoField)
- bigint自增列,必须填入参数 primary_key=True
注:当model中如果没有自增列,则自动会创建一个列名为id的列
from django.db import models
class UserInfo(models.Model):
# 自动创建一个列名为id的且为自增的整数列
username = models.CharField(max_length=32)
class Group(models.Model):
# 自定义自增列
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
SmallIntegerField(IntegerField):
- 小整数 -32768 ~ 32767
PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正小整数 0 ~ 32767
IntegerField(Field)
- 整数列(有符号的) -2147483648 ~ 2147483647
PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正整数 0 ~ 2147483647
BigIntegerField(IntegerField):
- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
BooleanField(Field)
- 布尔值类型
NullBooleanField(Field):
- 可以为空的布尔值
CharField(Field)
- 字符类型
- 必须提供max_length参数, max_length表示字符长度
TextField(Field)
- 文本类型
EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制
IPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
GenericIPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"
URLField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证 URL
SlugField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
CommaSeparatedIntegerField(CharField)
- 字符串类型,格式必须为逗号分割的数字
UUIDField(Field)
- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证
FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
FileField(Field)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
ImageField(FileField)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
width_field=None, 上传图片的高度保存的数据库字段名(字符串)
height_field=None 上传图片的宽度保存的数据库字段名(字符串)
DateTimeField(DateField)
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD
- auto_now_add=True 当在首次创建对象时自动将字段设置为现在。用于创建时间戳
- auto_add=True 每次保存对象时,自动将字段设置为现在。用于“最后修改”的时间戳
TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field)
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
FloatField(Field)
- 浮点型,精确的数不能用 FloatField
DecimalField(Field)
- 10进制小数,对于比较精确的数可以用 DecimalField
- 参数:
max_digits,小数总长度
decimal_places,小数位长度
BinaryField(Field)
- 二进制类型
2.2 字段参数
null 数据库中字段是否可以为空
db_column 数据库中字段的列名
default 数据库中字段的默认值
primary_key 数据库中字段是否为主键
db_index 数据库中字段是否可以建立索引
unique 数据库中字段是否可以建立唯一索引
unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引
unique_for_month 数据库中字段【月】部分是否可以建立唯一索引
unique_for_year 数据库中字段【年】部分是否可以建立唯一索引
verbose_name Admin中显示的字段名称
blank Admin中是否允许用户输入为空
editable Admin中是否可以编辑
help_text Admin中该字段的提示信息
choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)
error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息;
字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
如:{'null': "不能为空.", 'invalid': '格式错误'}
validators 自定义错误验证(列表类型),从而定制想要的验证规则
from django.core.validators import RegexValidator
from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
如:
test = models.CharField(
max_length=32,
error_messages={
'c1': '优先错信息1',
'c2': '优先错信息2',
'c3': '优先错信息3',
},
validators=[
RegexValidator(regex='root_\d+', message='错误了', code='c1'),
RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),
EmailValidator(message='又错误了', code='c3'), ]
)
1. null
用来保存空值,默认值 false。对于保存字符串的字段,尽量避免将其设置为 true,否则可能会导致出现是 null 或空字符串
2. blank
表示字段可以为空,默认 false。常用语表单输入验证是否为空,与数据库无关。
3. choices
页面上选择框标签,二维元组格式。两个参数,第一个为存储在数据库中,第二个显示在页面上内容。
数据库存储的是0 、1 、2,而页面上显示的是篮球、足球、羽毛球
hobby_choices = [(0, '篮球'), (1, '足球'), (2, '羽毛球'),]
hobby = models.IntegerField(
choices=hobby_choices
)
4. primary_key
主键,如果没有指定主键,那么 Django会自动创建一个 AutoField的自增字段,名为 id,并将其设置为主键。
primary_key 相当于 null=False和unique=True,即唯一且不能为空,你也可以自己将其他字段设置为空,若有必要。
示例
from django.db import models
class UserInfo(models.Model):
username = models.CharField(
null=True,
db_column='user',
max_length=32,
db_index=True,
verbose_name='用户名',
help_text='帮助信息',
default='',
)
hobby_choices = [(0, '篮球'), (1, '足球'), (2, '羽毛球'),]
hobby = models.IntegerField(
choices=hobby_choices
)
def __str__(self):
return self.username
2.3 元类 Meta
模型中的元类是指除了字段外的其他非必须内容,如修改数据表的名字,设置代理等。
使用方式
class User(models.Model):
...
class Meta:
verbose_name = '用户'
1. abstract
为 true 时,模型会被认为是一个抽象类,长用来作为其他模型的父类被继承。
2. app_label
如果没有在 settings 中注册 app,那么必须在元类中声名是属于哪个 app
app_label = 'polls'
3. base_manager_name
自定义模型的 _base_manager 管理器的名字,模型管理器是 Django 为模型提供的 API
4. db_table
指定模型生成数据表时,数据表的表名
db_table = 'user'
5. db_tablespace
自定义数据库表空间的名字。默认值是工程的DEFAULT_TABLESPACE设置
6. default_manager_name
自定义模型的_default_manager管理器的名字
7. default_related_name
反向查询时,默认我们使用的 `_set` 也就是源模型名字+下划线+set 方法查询,当我们定义了 default_related_name时,就可以使用它来反向查询。
8. ordering
指定该模型生成的所有对象的排序方式,接收一个字段名组成的元组或列表。默认按升序排列,如果在字段名前加上字符“-”则表示按降序排列,如果使用字符问号“?”表示随机排列。
ordering = ['pub_date'] # 表示按'pub_date'字段进行升序排列
ordering = ['-pub_date'] # 表示按'pub_date'字段进行降序排列
ordering = ['-pub_date', 'author'] # 表示先按'pub_date'字段进行降序排列,再按`author`字段进行升序排列。
9. permissions
该元数据用于当创建对象时增加额外的权限。它接收一个所有元素都是二元元组的列表或元组,每个元素都是(权限代码, 直观的权限名称)的格式。比如下面的例子:
permissions = (("can_deliver_pizzas", "可以送披萨"),)
10. default_permissions
Django默认给所有的模型设置('add', 'change', 'delete')的权限,也就是增删改。你可以自定义这个选项,比如设置为一个空列表,表示你不需要默认的权限,但是这一操作必须在执行migrate命令之前
11. proxy
若为 true,表示使用代理模式的模型继承方式。
12. required_db_vendor
声明模型支持的数据库。Django默认支持sqlite, postgresql, mysql, oracle
13. indexes
接收一个应用在当前模型上的索引列表
from django.db import models
class Customer(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
class Meta:
indexes = [
models.Index(fields=['last_name', 'first_name']),
models.Index(fields=['first_name'], name='first_name_idx'),
]
14. unique_together
等同于数据库的联合约束,无法应用多对多字段。
比如一张用户表,保存用户姓名、出生日期、地址等,现在有两个叫张伟的人,那么就可以使用联合唯一。
# 表示 name、birth_day、address 联合唯一,即不能在同一地方、同一时刻出生且都叫张伟
unique_together = (('name', 'birth_day', 'address'),)
14. verbose_name
给 Admin 提供人性化的名称,支持中文,如:
verbose_name = '用户' # 那么 Admin 中显示的就是 用户
15. label
只读元数据,不可修改,相当于 polls.Question
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 %}
Title
当访问 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('添加成功')
参考博客:
- Django实现省市县级联菜单
- django 自关联之 - 省市县
- Django实现 省 市 县 自关联的下拉级联
- django一对多、多对多模型、自关联的建立
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 [email protected]
# 反向查询(根据要查询的数据表名)
# 根据邮箱查询学生名字
# obj2 = models.StudentDetail.objects.filter(email='[email protected]')[0]
# print(obj2) # ]>
#
# print(obj2.student.student_name) # rose
############## 一对多(班级表和学生表) ##################
# 正向查询
# 根据学生名查询所属班级
# obj3 = models.Student.objects.get(student_name='rose')
# print(obj3.sc_id) # 1
# print(type(obj3.sc)) #
# 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) # , ]>
# for i in res:
# print(i.student_name) # john、lila
# 方法二
# ret = models.Student.objects.filter(sc=obj4).values('student_name') # 字典形式
# print(ret) #
# 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) # , ]>
# 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) #
# # 正向 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()**。
查询分为:
- 基于对象查询(相当于 SQL 中子查询)
- 先获取相关对象:
models.Models.objects.filter(过滤字段)
- 再通过对象(正向、反向)查询相关字段
- 先获取相关对象:
- 基于 QuerySet 和双下划线查询(相当于 SQL 中连表查询)
- 前面是过滤条件,后面跟要显示(查询)的字段
models.Model.objects.filter(过滤字段).values(要显示的字段)
一对一
- 正向查询:根据
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='[email protected]')[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. 模型继承
Django中所有的模型都必须继承 django.db.models.Model
模型,同样地我们也可以自己创建一个父模型用来保存公共部分,子模型继承父模型。这样的好处就是有时会省去很多重复代码。
同样地 Django 也支持继承多个父类。
Django 中三种继承方式:
- 抽象基类:被用来继承的模型被称为
Abstract base classes
,将子类共同的数据抽离,供子类复用,它不会创建实际的数据表 - 多表继承:
Multi-table inheritance
,每一个模型都有自己的数据库表 - 代理模型:如果你只想修改模型的Python层面的行为,并不想改动模型的字段,可以使用代理模型
4.1 抽象基类
只需在模型中元类添加 abstract=True
,即可将模型转换为抽象基类。但是它不会创建实际数据库表,只能用来被继承。
from django.db import models
class CommonInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
class Meta:
abstract = True
class User(CommonInfo):
email = models.EmailField(max_length=60)
模型 User 将会有 username、password、email
三个字段。
抽象基类元类
如果子类没有元类,那么它将继承基类的元类,同样地子类也可以对基类的元类进行拓展:
class CommonInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
class Meta:
abstract = True
ordering = ['username']
class User(CommonInfo):
email = models.EmailField(max_length=60)
# 继承基类的元类,并进行拓展
class Meta(CommonInfo.Meta):
db_table = 'username'
- 基类有元类,子类也没有的话,直接继承
- 基类有元类,子类也有,直接覆盖
- 子类可以添加额外元数据
- 基类的
abstract=true
不会被继承 - 基类的
db_table
元数据无效,因为抽象基类不会创建数据表
related_name 和 related_query_name
若抽象基类中存在 Foreignk
和 ManyToManyField
字段,且设置了 related_name
或 related_query_name
参数,其子类也将继承这两个参数。但是在查询时就会出现错误。
例如,对于 app01/models.py
中:
class Base(models.Model):
m2m = models.ManyToManyField(User, related_name="%(app_label)s_%(class)s_related", related_query_name="%(app_label)s_%(class)ss")
class Meta:
abstract = True
class ChildA(Base):
pass
class ChildB(Base):
pass
- app01.ChildA.m2m 字段:反向关系名(reverse name)为
app01_childa_related
;反向查询名(reverse query name)为app01_childas
- app01.ChildB.m2m 字段:分别为:
app01_childb_related
和app01_childbs
- 如果没有设置 related_name 或 related_query_name 就没有上述情况
4.2 多表继承
父类和子类都是可正常使用的模型,且都有自己的数据表,其本质为 一对一 关系。
class UserInfo(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
class PersonalDetail(UserInfo):
email = models.EmailField()
description = models.CharField(max_length=4000)
PersonalDetail
继承 UserInfo
,它会继承父类所有的字段,两个模型内部是一对一关系,如下所示:
>>> from polls.models import UserInfo, PersonalDetail
>>> q = PersonalDetail.objects.all()
>>> for i in q:
... i.name
...
'rose'
'lina'
多表继承之元类
多表继承中子类不会继承父类的元类,但是有两个元类数据除外: ordering、get_latest_by
,因此若希望不继承父类的元类的所有元数据,就需要指定或重写这两个元数据:
class PersonalDetail(UserInfo):
email = models.EmailField()
description = models.CharField(max_length=4000)
class Meta:
# 重写 ordering,移除父类元类的影响
ordering = []
4.3 多重继承
与 Python 一样,模型也可以继承多个父类模型,当有多个父类都含有 Meta 类时,那么只有第一个父类的会被继承,其余将被忽略掉。
Tips
- 尽量避免不用多重继承
- 当父类有相同 id 主键字段时,将会报错,因此需要给父类显示地添加
AutoField
字段。
class Base1(models.Model):
base1_id = models.AutoField(primary_key=True)
pass
class Base2(models.Model):
base2_id = models.AutoField(primary_key=True)
pass
class Child(Base1, Base2):
pass
4.4 代理模型
代理模型就是对源模型的一种代理,可以创建、删除、更新代理模型的实例,与源模型用的是同一张数据表,但是 它对源模型数据没有影响。
要想将一个模型设置为代理模型,只需在 Meta 类中设置 proxy=True
:
class A(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
class B(A):
# 代理模型
class Meta:
proxy = True
ordering = ['age']
def do_something(self):
pass
只会创建一张数据表,同时代理模型也可以操作数据表:
from .models import A, B
>>> A.objects.create(name='rose', age=18)
>>> q = B.objects.get(name='rose')
>>> q.age
18
# 对代理模型排序,按照年龄从小到大排列,被代理的模型,查询不会排序
obj_list = B.objects.all()
for obj in obj_list:
print(obj.age) # 17、18 、20
print(obj.name) # lila、rose、tom
Tips
- 代理模型必须继承自一个非抽象的基类,并且不能同时继承多个非抽象基类;
- 代理模型可以同时继承任意多个抽象基类,前提是这些抽象基类没有定义任何模型字段。
- 代理模型可以同时继承多个别的代理模型,前提是这些代理模型继承同一个非抽象基类。
4.5 总结
- 抽象基类不会创建数据表,直接设置
abstract=True
即可设置为抽象基类 - 多表继承实质是一对一关系,不会继承基类 Meta,除
ordering 和 get_latest_by
- 多表继承最好不要用
- 代理模型与被代理模型通用一张数据表,可以对代理数据进行创建、删除、修改等操作,但对源模型无影响
- 若基类是非抽象基类,基类与子类有相同名字的字段,那么将会覆盖子类(抽象基类除外)
# A 中的 name 将会覆盖 B 中 的 name,除非 A 是抽象基类
class A(models.Model):
name = models.CharField(max_length=32)
# class Meta:
# abstract = True
class B(A):
name = models.CharField(max_length=32)
组织模型
当模型有很多时,我们最好将模型分割开来,分类存储,这样有利于组织。我们可以利用包来组织模型。
在应用中创建一个名为 models
的包(pycharm 可以直接创建包,不需要手动创建 init.py文件),然后将模型分类到各个 .py 文件中,最后将其导入 __init__.py
文件中:
# app/models/__init__.py
from polls.models.modelone import User
5. 聚合分组
5.1 聚合 aggregate
aggregate()
返回一个字典,键为聚合值标识符,值为计算处理的聚合值。
使用时需要先导入内置函数:
from django.db.models import Avg, Sum, Max, Min, Count
示例:
from django.db.models import Avg, Sum, Max, Min, Count
>>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age')) # 指定键为 avg_age,也可以用默认的
{'avg_age': 18}
多个聚合:
from django.db.models import Avg, Sum, Max, Min, Count
>>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age'), max_length=Max('height'))
{'avg_age': 18, 'max_length': 182}
5.2 分组 annotate
ORM 中 values()
或 values_list()
选字段,就相当于原生 SQL 中 select
什么字段:
ret = models.Employee.objects.all() # 相当于 select * from employee
"""
SELECT `employee`.`id`, `employee`.`name`, `employee`.`age`, `employee`.`salary`, `employee`.`province`, `employee`.`dept` FROM `employee` LIMIT 21; args=()
"""
ret = models.Employee.objects.all().values("dept", "age")
"""
SELECT `employee`.`dept`, `employee`.`age` FROM `employee` LIMIT 21; args=()
"""
select
选中什么字段,group by
就只能是什么字段:
mysql> select name, Avg(salary) from app_employee group by name;
+------+-------------+
| name | Avg(salary) |
+------+-------------+
| rose | 2000.1 |
| lila | 3000.45 |
| john | 4000.56 |
| tom | 5005.78 |
+------+-------------+
连表分组查询
按照部门分组,查询每个部门的平均薪水:
# ORM 操作
# 前一个 values() 表示按照什么分组,后一个表示要显示的字段
>>> employee_list = models.Employee.objects.values('dept__name').annotate(avg=Avg('salary')).values('dept__name', 'avg')
>>> print(employee_list)
# 对应 SQL 语句
SELECT `app_department`.`name`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` INNER JOIN `app_department` ON (`app_employee`.`dept_id` = `app_department`.`id`) GROUP BY `app_employee`.`name`, `app_department`.`name` ORDER BY NULL LIMIT 21; args=()
- ORM 分组查询
按照国家分组,查询每个国家的平均薪水:
>>> employee_list = models.Employee.objects.values('addr').annotate(avg=Avg('salary')).values('addr', 'avg')
>>> print(employee_list)
SELECT `app_employee`.`addr`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` GROUP BY `app_employee`.`addr` ORDER BY NULL LIMIT 21; args=()
Tips
- ORM 中的连表查询(跨表)相当于 SQL 中的连表(
inner john
)查询 - ORM 查询,第一个
values(fields)
为分组字段,第二个values(fileds1, fields2
) 为要显示的字段 select
选中什么字段,group by
就只能是什么字段
5. F 查询和 Q 查询
5.1 F 查询
上面我们都是在比较字段与某个常亮,要想两个字段进行比较,那就要用到 F 查询
- 查询书的价格大于评论数的书籍
from django.db.models import F
models.Book.objects.filter(price__gt=F('comment_count'))
- 将书的价格整体提升 5 元
models.Book.objects.update(price=F('price')+5)
- F 对象和 F 对象以及常量之间可以进行加减乘除、取模等
models.Book.objects.update(price=F('price')*5)
5.2 Q 查询
Q 构建搜索条件(在这里解决了或、与的问题)
- Q 对象可以对关键字参数进行封装,从而更好地应用多个查询
obj1 = models.StudentDetail.objects.filter(Q(age=18)) # 构建过滤条件
- 组合使用 &、| 、~ 操作符
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) # ]>
# 原生 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;
- Q 对象 可以和关键字参数一起查询使用,不管一定要在关键字参数前面
models.StudentDetail.filter(Q(age=18), email__startswith='123')
- 应用
import datetime
obj = models.Class.objects.filter(Q(create_data=datetime.date(2019,2,15)))
print(obj)