Django之Models
1. 数据库的配置和使用
ORM是 “对象-关系-映射” 的简称。(Object Relational Mapping,简称ORM)
1.1 配置settings文件
django默认使用sqlite的数据库,并默认自带sqlite的数据库驱动
如果要更改数据库为MySQL,需要配置如下:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'books', # 数据库名称,必须事先创建好 'USER': 'root', # 数据库用户名 'PASSWORD': '', # 数据库密码 'HOST': '', # 数据库主机,留空默认为localhost 'PORT': '3306', # 数据库端口 } }
1.2 更改MySQL驱动
django默认的MySQL驱动为MySQLdb,而MySQLdb在py3中有问题,所以还需要更改MySQL驱动为pymysql
# 找到项目名文件下的__init__,在里面写入: import pymysql pymysql.install_as_MySQLdb()
1.3 在models中通过类创建数据库表
在对应app的models文件中创建数据库表
- 每个类就对应一张表
- 类的每个实例就对应于表中的一条记录
- 每个类中的属性就对应每张表中对应的每个字段
from django.db import models # Create your models here. class UserInfo(models.Model): # 这里类就对应于一张表,且必须继承models.Model id = models.AutoField(primary_key=True) # 当model中如果没有自增列,则自动会创建一个列名为id的列 name = models.CharField(max_length=16) age = models.IntegerField() current_date = models.DateField() """ 上面的几个类的属性通过ORM映射就对应成了: create table userinfo( id int primary key auto_increment, name varchar(16), age int, current_date date) """
1.4 在数据库中生成表结构
将上面的类生成真生的数据库中的表结构
python manage.py makemigrations # Django 会在相应的 app 的migrations文件夹下面生成 一个python脚本文件 python manage.py migrate # 生成数据库表 # 此时,对应app下面的migrations目录中出现一个0001_initial.py的文件,这个文件就是执行了上述指令之后产生的脚本文件,这个文件就是一个记录
2. 字段和参数
2.1 字段
CharField # 字符串字段, 用于较短的字符串 # CharField 必须有一个参数 maxlength, 限制该字段所允许的最大字符数. IntegerField # 用于保存一个整数 DecimalField # 一个浮点数. 必须 提供两个参数: # max_digits 总位数(不包括小数点和符号) # decimal_places 小数位数 # 要保存最大值为 999 (小数点后保存2位): # models.DecimalField(..., max_digits=5, decimal_places=2) # 要保存最大值一百万(小数点后保存10位): # models.DecimalField(..., max_digits=17, decimal_places=10) # max_digits大于等于17就能存储百万以上的数了 # admin 用一个文本框(<input type="text">)表示该字段保存的数据 AutoField # 一个 IntegerField, 添加记录时它会自动增长,通常不需要直接使用这个字段 # 自定义一个主键:my_id=models.AutoField(primary_key=True) # 如果不指定主键,系统会自动添加一个主键字段到 model BooleanField # A true/false field # admin 用 checkbox 来表示此类字段 TextField # 一个容量很大的文本字段 # admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框) EmailField # 一个带有检查Email合法性的 CharField,不接受 maxlength 参数 DateField # 一个日期字段 # 有下列额外的可选参数: # auto_now # 当对象被保存时(更新或者添加),自动将该字段的值设置为当前时间. # 通常用于表示 "last-modified" 时间戳. # auto_now_add # 当对象首次被创建时,自动将该字段的值设置为当前时间. # 通常用于表示对象创建时间. # (仅仅在admin中有意义...) DateTimeField # 一个日期时间字段. 类似 DateField 支持同样的附加选项 ImageField # 类似 FileField, 不过要校验上传对象是否是一个合法图片 # 它有两个可选参数:height_field和width_field,如果提供这两个参数,则图片将按提供的高度和宽度规格保存 FileField # 一个文件上传字段. # 要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. # 这个路径必须包含 strftime #formatting, # 该格式将被上载文件的 date/time替换(so that uploaded files don't fill up the given directory). # admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) . # # 在一个 model 中使用 FileField 或 ImageField 需要以下步骤: # 1) 在 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件 # 出于性能考虑,这些文件并不保存到数据库 # 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对WEB服务器用户帐号是可写的. # 2) 在 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项, # 以告诉 Django使用 MEDIA_ROOT 的哪个子目录保存上传文件. # 数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT). # 如果 ImageField 叫作 mug_shot, 就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径 URLField # 用于保存 URL. # 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且没有返回404响应). # admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) NullBooleanField # 类似 BooleanField, 不过允许 NULL 作为其中一个选项. # 推荐使用这个字段而不要用 BooleanField 加 null=True 选项 # admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据 XMLField # 一个校验值是否为合法XML的 TextField # 必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema 的文件系统路径. FilePathField # 可选项目为某个特定目录下的文件名. # 支持三个特殊的参数, 其中第一个是必须提供的.这三个参数可以同时使用. # path # 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目. # Example: "/home/images". # match # 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. # 注意这个正则表达式只会应用到 base filename 而不是路径全名. # Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif. # recursive # 可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录. # # match 仅应用于 base filename, 而不是路径全名 # FilePathField(path="/home/images", match="foo.*", recursive=True) # 会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif IPAddressField # 一个字符串形式的 IP 地址, (i.e. "24.124.1.30"). CommaSeparatedIntegerField # 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
2.2 参数
null # 如果为True,Django 将用NULL 来在数据库中存储空值,默认值是 False db_column # 数据库中字段的列名 default # 字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用 # 如果字段没有设置可以为空,将来如果后添加一个字段,这个字段就要给一个default值 primary_key # 如果为True,那么这个字段就是模型的主键。 # 如果没有指定任何一个字段的primary_key=True,Django会自动添加一个IntegerField字段做为主键 # 所以除非想覆盖默认的主键行为,否则没必要设置任何一个字段的primary_key=True。 unique # 如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的 # 就是建立唯一索引 db_index # 如果db_index=True 则代表着为此字段设置数据库索引 verbose_name # Admin中显示字段名称 blank # Admin中是否允许用户输入为空 # 如果为True,该字段允许不填。默认为False。 editable # Admin中是否可以编辑 help_text # Admin中该字段的提示信息 choices # 由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项 # 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框, # 而且这个选择框的选项就是choices 中的选项 # 如:gf = models.IntegerField(choices=[(0, 'shit'),(1, 'fuck'),],default=1) error_messages # 自定义错误信息(字典类型),从而定义想要的验证规则 # 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date # 如:{'null': "不能为空.", 'invalid': '格式错误'} DatetimeField、DateField、TimeField 这个三个时间字段,都可以设置如下属性: auto_now_add # 配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。 # auto_now # 配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间 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'), ] )
2.3 元信息
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32)
class Meta: # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 db_table = "table_name" # 联合索引 index_together = [ ("pub_date", "deadline"), ] # 联合唯一索引 unique_together = (("driver", "restaurant"),) # admin中显示的表名称 verbose_name # verbose_name加s verbose_name_plural
3. 多表关系及参数
3.1 外键关联
ForeignKey(ForeignObject) # ForeignObject(RelatedField) to, # 要进行关联的表名 to_field=None, # 要关联的表中的字段名称 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中是否显示关联数据
关于db_contraint
不加外键也是可以表示两张表之间的关系的,但是这样就不能使用ORM外键相关的方法了
所以如果单纯的讲外键换成一个其他字段类型,只是单纯的存着另外一个关联表的主键值是不能使用ORM外键方法的
db_contraint只加两者的关系,而没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果要求建立外键但是不能有强制的约束关系,那么就可以将这个参数改为False
customer = models.ForeignKey(verbose_name='关联客户', to='Customer', db_constraint=False)
on_delete参数
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", to_field="id" on_delete=models.SET(func),)
3.2 一对一关联
一对一其实就是 一对多 + 唯一索引
OneToOneField(ForeignKey) to, # 要进行关联的表名 to_field=None # 要关联的表中的字段名称 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为
当两个类之间有继承关系时,默认会创建一个一对一字段
如下会在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)
3.3 多对多关联
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, # 默认创建第三张表时,数据库中表的名称
4. ORM单表操作
4.1 增:添加表记录
1)方式一
在Views的index函数中操作:
def index(request): # 实例化一个对象就是一条记录 student_obj = models.Student( name='hgzero', age=18 ) student_obj.save() # 将此记录增加到数据表中 return render(request,'index.html')
2)方式二
通过objects控制器对象来调用数据表相应的增删改查方法。
它可以创建一个新对象保存到对应的数据表中,并返回这个新创建的对象。
这个models类的对象就称之为model对象
new_obj = models.Student.objects.create( name='hgzero', age=19 ) print(new_obj) # Student object print(new_obj.name) # hgzero
3)方式三:批量创建
批量插入很多数据:
obj_list = [models.Student(name=f'stu{i}', age=20) for i in range(1, 21)] models.Student.objects.bulk_create(obj_list) # 调用bulk_create来批量插入数据
4.2 查:获取行记录
1)all() 全部取出
- 通过 objects 控制器调用,返回QuerySet类型,里面有很多个Student类的对象也就是model对象
- QuerySet类似于列表,可以循环遍历取值
all_objs = models.Student.objects.all() ''' < QuerySet[ < Student: Student object >, < Student: Student object >, < Student: Student object >, ...'...(remaining elements truncated)...' ]> '''
2)filter(条件) 条件查询
- 通过object控制器调用,返回QuerySet类型
- 如果查询不到内容不会报错,返回一个空的QuerySet集合
objs = models.Student.objects.filter(id=2) objs = models.Student.objects.filter(name='xxx', age=18) # 多条件查询 print(objs) # <QuerySet [<Student: hgzero>]> print(objs[0].id) # 可以通过索引取值
3)get(条件) 条件查询
- 通过object控制器调用,返回model对象
- 通过get条件查询,查询的结果有且只有1个
- object.getlist(id=1) 这样可以取多个结果
obj = models.Student.objects.get(id=1) # 返回的是model对象,且查询结果只能有一个
4)exclude 排除
- 通过object对象或者QuerySet集合调用,返回QuserySet集合
# 排除name为hgzero的行记录,将剩下所有的返回 objs = models.Student.objects.filter(age=20).exclude(name='hgzero')
5)order_by 排序
- 通过object对象或者QuerySet集合调用,返回QuserySet集合
# object对象调用 objs = models.Student.objects.order_by('age') # 通过姓名升序排列 # queryset集合调用 objs = models.Student.objects.all().order_by('age') # 通过姓名升序排列
6)reverse 反转
- 通过order_by返回的QuerySet集合调用,返回一个QuerySet集合
# 只能通过order_by返回的QuerySet集合调用 objs = models.Student.objects.order_by('id').reverse()
7)count 计数
- 通过QuerySet集合调用,返回一个元素个数
num = models.Student.objects.all().count() # 返回的是记录的条数 num = models.Student.objects.filter(age=20).count()
8)first 返回第一个model对象
- 通过QuerySet集合调用,返回第一个model对象
obj = models.Student.objects.filter(age=20).first()
9)last 返回最后一个model对象
- 通过QuerySet集合调用,返回最后一个model对象
obj = models.Student.objects.filter(age=20).last()
10)exists 判断是否存在
- 通过QuerySet集合调用,返回bool值
flag = models.Student.objects.filter(age=25).exists()
11)values_list
- 通过QuerySet集合调用,返回一个QuerySet集合
- 这个QuerySet里面的元素是元组的形式,而不是model对象
query_tuple = models.Student.objects.filter(age=20).values_list() # <QuerySet [(4, 'stu1', 20), (24, 'stu2', 20), (27, 'stu3', 20)]> query_tuple = models.Student.objects.filter(age=20).values_list('name','age') # 指定想要获取的字段
12)values
- 通过QuerySet集合调用,返回一个QuerySet集合
- 这个QuerySet集合里面是字典的形式,而不是model对象
query_dict = models.Student.objects.filter(age=19).values() # <QuerySet [{'id':3, 'name':'stu1', 'age':19}, {'id':25, 'name':'stu2', 'age':19}]> query_dict = models.Student.objects.filter(age=19).values('name', 'age') # <QuerySet [{'name':'stu1', 'age':19}, {'name':'stu2', 'age':19}]>
13)distinct 去重
- 通过QuerySet集合调用,返回一个QuerySet集合
- 对整个对象去重是没有意义的,因为只要有一个字段不同,都不是重复的
query_objs = models.Student.objects.filter(age=20).distinct() query_objs = models.Student.objects.filter(age=20).values('age').distinct() # 去重一般都用于values或者values_list
14)group by 分组
from django.db.models import Count, Min, Max, Sum models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num')) # SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id"
15)双下划线模糊查询
query_objs = models.Student.objects.filter(age__gt=19) # 大于 query_objs = models.Student.objects.filter(age__gte=19) # 大于等于 query_objs = models.Student.objects.filter(age__lt=20) # 小于 query_objs = models.Student.objects.filter(age__lte=20) # 小于等于 query_objs = models.Student.objects.filter(age__range=[18, 20]) # 范围 左右都包含 query_objs = models.Student.objects.filter(name__contains='xiao') # 针对字符串类型,内容含有 query_objs = models.Student.objects.filter(name__icontains='xiao') # 针对字符串类型,内容含有 不区分大小写 query_objs = models.Student.objects.filter(name__startswith='x') # 匹配以x开头 query_objs = models.Student.objects.filter(name__istartswith='x') # 匹配以x开头,不区分大小写 query_objs = models.Student.objects.filter(name__endswith='o') # 匹配以x结尾 query_objs = models.Student.objects.filter(name__iendswith='o') # 匹配以x结尾,不区分大小写 models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in
16)日期
创建日期字段:
class Birthday(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=16)
date = models.DateField()
插入日期数据:
models.Birthday.objects.create(name='stu1', date='2020-06-28')
models.Birthday.objects.create(name='stu2', date='2020-07-02')
查询2020年6月出生的人:
query_objs = models.Birthday.objects.filter(date__year='2020',date__month='06')
print(query_objs) # <QuerySet [<Birthday: stu1>]>
5.5 删:删除行记录
1)调用model对象删除
models.Student.objects.get(id=20).delete()
2)调用QuerySet集合删除
models.Student.objects.filter(age=20).delete()
5.6 改:更新行记录
count = models.Student.objects.filter(name='stu1').update(age=20)
6. ORM多表操作
6.1 多对多表的创建方式
1)自行创建第三张表
- 注意:自行创建第三张表就无法使用orm提供的set、add、remove、clear方法来管理多对多的关系了
class Book(models.Model): title = models.CharField(max_length=32, verbose_name="书名") class Author(models.Model): name = models.CharField(max_length=32, verbose_name="作者姓名") # 自己创建第三张表,分别通过外键关联书和作者 class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book")
2)通过ManyToManyField自动创建
class Book(models.Model): title = models.CharField(max_length=32, verbose_name="书名") # 通过ORM自带的ManyToManyField自动创建第三张表 class Author(models.Model): name = models.CharField(max_length=32, verbose_name="作者姓名") books = models.ManyToManyField(to="Book", related_name="authors") #自动生成的第三张表我们是没有办法添加其他字段的
3)设置ManyToManyField并指定自行创建的第三张表(中介模型)
- 当需要在第三种关系表中存储额外的字段时,就要使用第三种方式
- 第三种方式还是可以使用多对多关联操作的接口(all、add、clear等等)
class Book(models.Model): title = models.CharField(max_length=32, verbose_name="书名") # 自己创建第三张表,并通过ManyToManyField指定关联 class Author(models.Model): name = models.CharField(max_length=32, verbose_name="作者姓名") books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book")) # through_fields接受一个2元组('field1','field2'): # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名 class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book") #可以扩展其他的字段了 class Meta: unique_together = ("author", "book")
6.2 添加表记录(一对多)
- 注意:对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
1)方式一
# 拿到nid为1的出版社对象 publish_obj=Publish.objects.get(nid=1) # publish_obj作为值传给publish,其实就是自动将publish字段变成publish_id,然后将publish_obj的id给取出来赋值给publish_id字段
# 注意:如果不是publish类的对象肯定会报错的 book_obj=Book.objects.create(title="动物农场",publishDate="2020-06-04",price=100,publish=publish_obj)
2)方式二
# 直接可以写id值,注意字段属性的写法和上面不同,这个是publish_id=xxx,上面是publish=xxx book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish_id=1)
6.3 添加表记录(多对多)
1)方式一
- 多对多一般在前端页面上使用的时候是多选下拉框的样子,来给用户选择多个数据,这里可以让用户选择多个书籍,多个作者
# 当前生成的书籍对象 book_obj=Book.objects.create(title="跟某孩和某哥学Linux",price=200,publishDate="2012-11-12",publish_id=1)
# 为书籍绑定作者对象 mage=Author.objects.filter(name="mage").first() # 在Author表中主键为2的纪录,注意取的是author的model对象 oldboy=Author.objects.filter(name="oldboy").first() # 在Author表中主键为1的纪录
# 这个自动生成的第三张表通过models是获取不到的,用不了的
# 但是如果知道这个表的名字,通过原生sql语句可以进行书的添加,
# 所以要通过orm间接的给第三张表添加数据,如果是手动添加的第三张表则是可以直接给第三张表添加数据的
# 绑定多对多关系,即向关系表book_authors中添加纪录,给书添加两个作者,以下语法就是告诉orm给第三张表添加两条数据 book_obj.authors.add(mage,oldboy) # 将某些特定的 model 对象添加到被关联对象集合中 == book_obj.authors.add(*[]) # book_obj是书籍对象,authors是book表里面那个多对多的关系字段名称 # 其实orm就是先通过book_obj的authors属性找到第三张表,然后将book_obj的id值和两个作者对象的id值组合成两条记录添加到第三张表里面去
2)方式二
book_obj.authors.add(1,2) book_obj.authors.add(*[1,2]) # 这种方式用的最多,因为一般是给用户来选择,用户选择是多选的,选完给你发送过来的就是一堆的id值
6.4 多表的其他操作
1)多对多关系其他常用API
book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除 == book_obj.authors.remove(*[1,2]),将多对多的关系数据删除 book_obj.authors.clear() # 清空被关联对象集合 book_obj.authors.set() # 先清空再设置
2)删除示例
book_obj = models.Book.objects.filter(nid=4)[0] book_obj.authors.remove(2) # 将第三张表中的这个book_obj对象对应的那个作者id为2的那条记录删除 book_obj.authors.clear() # 先清除掉所有的关系数据,然后只给这个书对象绑定这个id为2的作者,所以只剩下一条记录
# 3-->2,比如用户编辑数据的时候,选择作者发生了变化,那么需要重新选择,所以就可以先清空,然后再重新绑定关系数据
# 注意这里写的是字符串,数字类型不可以 book_obj.authors.set('2')
# 这么写也可以,但是列表中的元素是字符串,列表前面没有* book_obj.authors.set(['1',])
3)更新&删除
# 更新 book_obj = models.Book.objects.get(id=1) # 获取一个书籍对象 data = {'title':'xxx','price':100} # 这个书籍对象更新后的数据 models.Book.objects.filter(id=n).update(**data) # 将新数据更新到原来的记录中 book_obj.authors.set(author_list) # 将数据和作者的多对多关系加上 # 删除 models.Book.objects.filter(id=1).delete()
7. 基于对象的跨表查询
7.1 一对多查询
基于Publish和Book表进行查询
- 正向查询按字段:book.publish
- 反向查询表名小写_set.all():pub_obj.book_set.all()
1)正向查询
- 按字段:publish
- 关联属性字段所在的表查询被关联表的记录就是正向查询,反之就是反向查询
book_obj=Book.objects.filter(pk=1).first() # book_obj.publish 是主键为1的书籍对象关联的出版社对象,book对象.外键字段名称 print(book_obj.publish.city)
2)反向查询
- 按表名:book_set
- 因为加上_set是因为反向查询的时候,查询出来的可能是多条记录的集合
publish=Publish.objects.get(name="庆丰出版社") # publish.book_set.all() : 与庆丰出版社关联的所有书籍对象集合,写法:小写的表名_set.all(),得到queryset类型数据 book_list=publish.book_set.all() for book_obj in book_list: print(book_obj.title)
7.2 一对一查询
基于Author与AuthorDetail
- 正向查询按字段:mege.authorDetail
- 反向查询按表名小写:authorDetial.author
1)正向查询
- 按字段:authorDetail
mage=Author.objects.filter(name="mage").first() print(mage.authorDetail.telephone) # mage.authorDeail就拿到了这个对象,因为一对一找到的就是一条记录 # 写法:作者对象.字段名,就拿到了那个关联对象
2)反向查询
- 按表名:author
- 不需要_set,因为一对一正向反向都是找到一条记录
# 查询所有住址在北京的作者的姓名 authorDet=AuthorDetail.objects.filter(addr="beijing")[0] authorDet.author.name
7.3 多对多查询
- 正向查询按字段:book.authors.all()
- 反向查询按表名小写_set.all():mage.book_set.all()
1)正向查询
- 按字段:author
# 动物农场所有作者的名字以及手机号 book_obj=Book.objects.filter(title="动物农场").first() authors=book_obj.authors.all() for author_obj in authors: print(author_obj.name,author_obj.authorDetail.telephone)
2)反向查询
- 按表名:book_set
author_obj=Author.objects.get(name="mage") book_list=author_obj.book_set.all() #与作者mage相关的所有书籍 for book_obj in book_list: print(book_obj.title)
3)注意
- 可以通过在ForeignKey()和ManyToManyField的定义中设置related_name的值来修改xxxx_set的名称
- 反向查询时,如果定义了related_name ,则用related_name定义的值来替换 表名
publish = ForeignKey(Blog, related_name='bookList')
8. 基于双下划线的跨表查询
- 基于双下划线的查询能自动确认JOIN关系,其实就是不断的join
- 正向查询按字段,反向查询按表名小写用来告诉ORM引擎 join 哪张表
8.1 一对多查询
# 查询苹果出版社出版过的所有书籍的名字与价格(一对多) # 正向查询 按字段:publish queryResult=Book.objects # 通过__告诉orm将book表和publish表进行join,然后找到所有记录中publish.name='苹果出版社'的记录(注意publish是属性名称)
# 然后select book.title,book.price的字段值 .filter(publish__name="苹果出版社") .values_list("title","price") # values或者values_list # 反向查询 按表名:book queryResult=Publish.objects .filter(name="苹果出版社") .values_list("book__title","book__price")
8.2 多对多查询
# 查询mage出过的所有书籍的名字(多对多) # 正向查询 按字段:authors: queryResult=Book.objects .filter(authors__name="mage") .values_list("title") # 反向查询 按表名:book queryResult=Author.objects .filter(name="mage") .values_list("book__title","book__price")
8.3 一对一查询
# 查询mage的手机号 # 正向查询 ret=Author.objects.filter(name="mage").values("authordetail__telephone") # 反向查询 ret=AuthorDetail.objects.filter(author__name="mage").values("telephone")
8.4 连续跨表
# 查询人民出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 queryResult=Book.objects .filter(publish__name="人民出版社") .values_list("title","authors__name") # 反向查询 queryResult=Publish.objects .filter(name="人民出版社") .values_list("book__title","book__authors__age","book__authors__name") # 手机号以151开头的作者出版过的所有书籍名称以及出版社名称 # 方式1: queryResult=Book.objects .filter(authors__authorDetail__telephone__regex="151") .values_list("title","publish__name") # 方式2: ret=Author.objects .filter(authordetail__telephone__startswith="151") .values("book__title","book__publish__name")
8.5 性能相关
1)select_related
def select_related(self, *fields) # 性能相关:表之间进行join连表操作,一次性获取关联的数据 model.tb.objects.all().select_related() model.tb.objects.all().select_related('外键字段') model.tb.objects.all().select_related('外键字段__外键字段')
2)prefetch_related
def prefetch_related(self, *lookups) # 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。 # 获取所有用户表 # 获取用户类型表where id in (用户表中的查到的所有用户ID) models.UserInfo.objects.prefetch_related('外键字段')
9. 聚合&分组
9.1 聚合
语法:aggregate(*args, **kwargs)
aggregate()是QuerySet的一个终止子句,它返回一个包含一些键值对的字典。
键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的,如果要想为聚合值指定一个名称,可以向聚合子句提供它。
# 计算所有图书的平均价格 from django.db.models import Avg Book.objects.all().aggregate(Avg('price')) # 或者给它起名字:aggretate(a=Avg('price')) # >>> {'price__avg': 34.35} Book.objects.aggregate(average_price=Avg('price')) # >>> {'average_price': 34.35} # 查询所有图书价格的最大值和最小值 from django.db.models import Avg, Max, Min Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
#count('id'),count(1)也可以统计个数,Book.objects.all().aggregete和Book.objects.aggregate(),都可以 # >>> {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
9.2 分组
- annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)
1)单表分组查询
# 查询每一个部门名称以及对应的员工数 emp: id name age salary dep 1 alex 12 2000 销售部 2 egon 22 3000 人事部 3 wen 22 5000 人事部 # sql语句的写法: select dep,Count(*) from emp group by dep; # ORM的写法: emp.objects.values("dep").annotate(c=Count("id") # 注意:annotate里面必须写个聚合函数,不然没有意义,并且必须有个别名=,别名随便写,但是必须有
# 用哪个字段分组,values里面就写哪个字段, annotate其实就是对分组结果的统计,统计你需要什么 ''' select dep,count('id') as c from emp grouby dep; # 原生sql语句中的as c,不是必须有的 '''
2)多表分组查询
# 查询每一个部门名称以及对应的员工数 emp: id name age salary dep_id 1 alex 12 2000 1 2 egon 22 3000 2 3 wen 22 5000 2 dep: id name 1 销售部 2 人事部 emp-dep: id name age salary dep_id id name 1 alex 12 2000 1 1 销售部 2 egon 22 3000 2 2 人事部 3 wen 22 5000 2 2 人事部 # sql语句的写法: select dep.name,Count(*) from emp left join dep on emp.dep_id=dep.id group by dep.id # ORM的写法: dep.objetcs.values("id").annotate(c=Count("emp")).values("name","c") ret = models.Emp.objects.values('dep_id','name').annotate(a=Count(1)) ''' SELECT `app01_emp`.`dep_id`, `app01_emp`.`name`, COUNT(1) AS `a` FROM `app01_emp` GROUP BY `app01_emp`.`dep_id`, `app01_emp`.`name` ''' # <QuerySet [{'dep_id': 1, 'name': 'alex', 'a': 1}, {'dep_id': 2, 'name': 'egon', 'a': 1}, {'dep_id': 2, 'name': 'wen', 'a': 1}]>, # 注意,这里如果写了其他字段,那么只有这两个字段重复,才算一组,合并到一起来统计个数
总结:跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询,既然是join连接,就可以使用双下划线进行连表了。
#单表: # 查询每一个部门的id以及对应员工的平均薪水 ret = models.Emp.objects.values('dep_id').annotate(s=Avg('salary')) # 查询每个部门的id以及对对应的员工的最大年龄 ret = models.Emp.objects.values('dep_id').annotate(a=Max('age')) # Emp表示表,values中的字段表示按照哪个字段group by,annotate里面是显示分组统计的是什么 #连表: # 查询每个部门的名称以及对应的员工个数和员工最大年龄 ret = models.Emp.objects.values('dep__name').annotate(a=Count('id'),b=Max('age'))
# 注意,正向与反向的结果可能不同,如果反向查的时候,有的部门还没有员工,那么他的数据也会被统计出来,只不过值为0,
# 但是正向查的话只能统计出来有员工的部门的相关数据,因为通过你是员工找部门,而不是通过部门找员工,结果集里面的数据个数不同,但是你想要的统计结果是一样的
#<QuerySet [{'a': 1, 'dep__name': '销售部', 'b': 12}, {'a': 3, 'dep__name': '人事部', 'b': 22}]> #使用双下划线进行连表,然后按照部门名称进行分组,然后统计员工个数和最大年龄,最后结果里面显示的是部门名称、个数、最大年龄
# 注意:如果values里面有多个字段的情况:ret = models.Emp.objects.values('dep__name','age').annotate(a=Count('id'),b=Max('age'))
# 是按照values里面的两个字段进行分组,两个字段同时相同才算是一组,看下面的sql语句
''' SELECT `app01_dep`.`name`, `app01_emp`.`age`, COUNT(`app01_emp`.`id`) AS `a`, MAX(`app01_emp`.`age`) AS `b`
FROM `app01_emp` INNER JOIN `app01_dep` ON (`app01_emp`.`dep_id` = `app01_dep`.`id`)
GROUP BY `app01_dep`.`name`, `app01_emp`.`age`;
'''
10. F&Q查询
10.1 F查询
如果要对两个字段的值做比较,就需要用到F查询。F()的实例可以在查询中引用字段,来比较同一个model实例中两个不同字段的值。
# 查询评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commentNum__lt=F('keepNum')) # Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作 # 查询评论数大于收藏数2倍的书籍 Book.objects.filter(commentNum__lt=F('keepNum')*2) # 修改操作也可以使用F函数,比如将每一本书的价格提高30元 Book.objects.all().update(price=F("price")+30)
10.2 Q查询
filter() 等方法中的关键字参数查询都是一起“AND”的,如果需要自行更复杂的查询(例如OR语句),就可以使用Q对象。
from django.db.models import Q Q(title__startswith='Py')
11. ORM执行原生SQL语句
11.1 执行原生查询
raw() 管理器方法用于原始的SQL查询,并返回模型的实例。
raw() 语法查询必须包含主键。
# 执行原生SQL for p in Person.objects.raw('SELECT * FROM myapp_person') print(p) # raw() 查询可以查询其他表的数据 ret = models.Student.objects.raw('select id,tname as xxoo from app02_teacher') for i in ret: print(i.id, i.xxoo) # raw() 方法自动将查询字段映射到模型字段 # 还可以通过translations参数指定一个把查询的字段名和ORM对象实例的字段相对应的字典 d = {'tname': 'xxoo'} ret = models.Student.objects.raw('select * from app02_teacher', translations=d) for i in ret: print(i.id, i.sname, i.xxoo) # 原生SQL还可以使用参数 # 注意不要自己使用字符串格式化拼接SQL语句,防止SQL注入 d = {'tname': 'haha'} ret = models.Student.objects.raw('select * from app02_teacher where id > %s', translations=d, params=[1,]) for i in ret: print(i.id, i.sname, i.haha)
11.2 直接执行自定义SQL
有时候需要将执行DELETE、INSERT以及UPDATE操作,可以直接访问数据库,完全避开模型层。我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库。
from django.db import connection, connections cursor = connection.cursor() # cursor = connections['default'].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) ret = cursor.fetchone()
12. Django外部脚本使用models
如果想通过自己创建的python文件在django项目中使用django的models,那么就需要调用django的环境。
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings") import django django.setup() from app01 import models #引入也要写在上面三句之后 books = models.Book.objects.all() print(books)