目录
ORM 介绍
创建 ORM 类
增删改查
进阶查询
类的字段和参数
ORM 连表的几种类型
ORM 连表操作
浅谈 ORM 查询性能
Django 自带 ContentType 表
其他小技巧
参考博客
ORM 介绍
ORM 的两种方式
db first 先连接数据库 -> ...
code first 先创建类 -> sqlachemy,Django, 大多数都是
- Django ORM
- ORM:Object Relational Mapping(关系对象映射)
类名 ->> 数据库中的表名
类属性 ->> 数据库里的字段
类实例 ->> 数据库表里的一行数据
obj.name..... ->> 类实例对象的属性
Django ORM 的优势: Django 的 ORM 操作本质上会根据对接的数据库引擎, 翻译成对应的 sql 语句; 所有使用 Django 开发的项目无需关心程序底层使用的是 MySQL,Oracle,SQLite...., 如果数据库迁移, 只需要更换 Django 的数据库引擎即可
QuerySet 数据类型介绍
QuerySet 特点:
可迭代的
可切片
惰性计算: 等于一个生成器,.objects.all()或者. filter()等都只是返回了一个 QuerySet 的查询结果集对象, 它并不会马上执行 sql, 而是当调用 QuerySet 的时候才执行.
缓存机制: 每一次数据库查询结果 QuerySet 都会对应一块缓存, 再次使用该 QuerySet 时, 不会发生新的 SQL 操作
这样减小了频繁操作数据库给数据库带来的压力
但是有时候取出来的数据量太大会撑爆缓存, 可以使用迭代器解决这个问题:
models.Publish.objects.all().iterator()
创建 ORM 类
1. 在 models 里创建表的类
- /App/models.py
- from django.db import models
- # 表名为 app01_userinfo
- class UserInfo(models.Model):
- # 自动创建 id 列, 自增, 主键
- # 列名, 字符串类型, 指定长度
- username = models.CharField(max_length=32)
- password = models.CharField(max_length=64)
- email = models.EmailField(max_length=19)
类的字段和参数详见字段和参数
2. 注册 App
- /./settings.py
- INSTALLED_APPS = [
- ...,
- 'app01',
- ]
3. 执行命令, 每次更改表结构都要重复一遍
python manage.py makemigrations -> 生成表结构的缓存
python manage.py migrate -> 创建表结构
4. 默认使用 sqlite3 数据库, 可修改为 MySQL
/./settings.py -> DATABASES
********** 注意 ***********
Django 默认使用 MySQLdb 模块链接 MySQL
主动修改为 pymysql, 在 project 同名文件夹下的__init__文件中添加如下代码即可:
- import pymysql
- pymysql.install_as_MySQLdb()
增删改查
1. 增
- /App/views.py
- from app01 import models
- def ORM(request):
- # 直接传入参数
- models.UserInfo.objects.create(username='root',password='123')
- # 传入字典
- dic = {'username': 'eric', 'password': '666'}
- models.UserInfo.objects.create(**dic)
- # 另一种增加方式
- obj = models.UserInfo(username='alex',password='123')
- obj.save()
2. 查
result = models.UserInfo.objects.all()
result = models.UserInfo.objects.filter(user='root',psd='123') -> filter 传入字典也可 **dic
=> QuerySet, Django 的一种列表, [], 内部元素是. obj => [obj(id,username),obj]
- # 转化为字典输出
- .all().values('id','caption') -> [{'id:1,'username':'alex'},{},{}]
- # 转化为 tuple 输出
- .all().values_list('id','caption') -> [(1,'alex'),(),()]
- # 取第一个 obj
.filter(xxx).first() -> 不存在返回 None
=> 用 get 取单条数据, 如果不存在, 直接报错
- => models.UserInfo.objects.get(id=nid)
- # 计数
- .filter(name='seven').count()
- # 切片
- .all()[10:20]
- .all()[::2]
- .all()[6] # 索引
- # 去重
- .distinct()
- # 排序
- .filter(name='seven').order_by('id') -> asc
- .filter(name='seven').order_by('-id') -> desc
3. 删
models.UserInfo.objects.filter(username="alex").delete()
4. 改
- models.UserInfo.objects.filter(id=3).update(password="69") # 可添加 **kwargs 形式
- # 或者先查找对象再修改保存
- obj = models.tb.objects.get(id=1)
- obj.c1 = '111'
- obj.save() # 修改单条数据
特殊的判断语句(神奇的双下划线 1)
- # 大于小于
- .filter(id__gt=1) ->> 1
- .filter(id=1) -> = 1
- .filter(id__lt=1) -> <1
- .filter(id__lte=1) -> <= 1
- .filter(id__gte=1) ->>= 1
.exclude(id__gt=1) -> != 1 exclude 除了... 与 filter 相反
- .filter(id__gt=1, id__lt=10) -> 1<x <10
- # 范围 range
- .filter(id__range=[1,3]) -> [1~3] bettwen + and
- # 范围 in
- .filter(id__in=[1,2,3]) -> in [1,2,3]
- .exclude(id__in=[1,2,3]) -> in [1,2,3]
- # 是否为空
- .filter(name__isnull=True)
- # 包含, 开头, 结尾 __startswith, istartswith, endswith, iendswith
- .filter(name__contains="ven")
- .filter(name__icontains="ven") # i 忽略大小写
- # regex 正则匹配, iregex 不区分大小写
- .get(title__regex=r'^(An?|The) +')
- .get(title__iregex=r'^(an?|the) +')
- # date
- .filter(pub_date__date=datetime.date(2005, 1, 1))
- .filter(pub_date__date__gt=datetime.date(2005, 1, 1))
- # year,month,day,week_day
- .filter(pub_date__year=2005)
- .filter(pub_date__year__gte=2005)
- # hour,minute,second
- .filter(timestamp__hour=23)
- .filter(time__hour=5)
- .filter(timestamp__hour__gte=12)
进阶查询
F 模块, 用于获取对象中的某一字段 (列) 的值, 并且对其进行操作;
- from django.db.models import F # 首先导入 F 模块
- models.Book.objects.all().update(price=F('price')+1) # 每一本书的价格上调 1 块钱
Q 模块, 用于构造复杂的查询条件, 使用逻辑关系 (& 与,| 或,~ 非) 组合进行多条件查询;
虽然 filter 中可以使用 , 隔开表示关系与, 但没法表示或非的关系
- from django.db.models import Q # 导入 Q 模块
- # 方式一:
- .filter( Q(id__gt=10) ) ->
- .filter( Q(id=8) | Q(id__gt=10) ) -> or
- .filter( Q( Q(id=8) | Q(id__gt=10) ) & Q(caption='root') ) -> and, or
- # 方式二:
- # 可以组合嵌套
- # q1 里面的条件都是 or 的关系
- q1 = Q()
- q1.connector = 'OR'
- q1.children.append(('id', 1))
- q1.children.append(('id', 10))
- q1.children.append(('id', 9))
- # q2 里面的条件都是 or 的关系
- q2 = Q()
- q2.connector = 'OR'
- q2.children.append(('c1', 1))
- q2.children.append(('c1', 10))
- q2.children.append(('c1', 9))
- # con 通过 and 的条件把 q1 和 q2 联系到一块
- con = Q()
- con.add(q1, 'AND')
- con.add(q2, 'AND')
- models.tb.objects.filter(con)
实例: 查询作者姓名中包含 方 / 少 / 伟 / 3 字, 书名不包含伟, 并且出版社地址以山西开头的书
- book=models.Book.objects.filter(
- Q(
- Q(author__name__contains='方') |
- Q(author__name__contains='少') |
- Q(author__name__contains='伟') |
- Q(title__icontains='伟')
- ) &
- Q(publish__addr__contains='山西')
- ).values('title')
注意: Q 查询和非 Q 查询混合使用, 非 Q 查询一定要放在 Q 查询后面
extra 方法
对不同的数据库引擎可能存在移植问题(因为你在显式的书写 SQL 语句), 尽量避免使用 extra
a. 映射
- - select={'new_id':select count(1) from app01_usertype where id>%s'}
- - select_params=[1,]
- # 例:
- models.UserInfo.objects.all().extra(
- select={
- 'n':"select count(1) from app01_utype WHERE id=%s or id=%s",
- 'm':"select count(1) from app01_uinfo WHERE id=%s or id=%s",
- },
- select_params=[1,2,3,4]
- )
b. 条件
- - where=["foo='a'OR bar ='a'","baz = '%s'"],
- - params=['Lennon',]
c. 表
- tables=["app01_usertype"]
d. 排序
- - order_by = ['-id']
- # 例 1:
- models.UserInfo.objects.extra(
- select={'new_id':select count(1) from app01_usertype where id>%s'},
- select_params=[1,],
- where=['age>%s'],
- params=[18,],
- order_by=['-age'],
- tables=["app01_usertype']
- )
-> 相当于:
- '''
- select
- app01_userinfo.id,
- (select count(1) from app01_usertype where id>1) as new_id
- from
- app01_userinfo,
- app01_usertype
- where
- app01_userinfo.age>18
- order by
- app01_userinfo.age desc
- '''
- # 例 2:
- current_user = models.UserInfo.objects.filter(username=username).first() # 当前用户
- 1,models.Article.objects.all() # 查出每一篇文章
- 2,models.Article.objects.all().filter(user=current_user) # 查出当前用户的所有文章
- 3,models.Article.objects.all().filter(user=current_user).extra(select={"filter_create_date":"strftime('%%Y/%%m',create_time)"}).values_list("filter_create_date")
- # 查出当前用户的所有文章的 create_time, 并且只取出年份和月份
- 执行原生 SQL 的三种方式
- 1. 使用 extra 方法
- 结果集修改器, 一种提供额外查询参数的机制
- 依赖 model 模型
- 2. 使用 raw 方法
- 执行原始 sql 并返回模型
- 依赖 model 多用于查询
- book = Book.objects.raw("select * from hello_book")
- for item in book:
- print(item.title)
- 3. 使用 cursor 游标
- 不依赖 model
- from django.db import connection, connections
- cursor = connection.cursor()
- # 或 cursor = connections['default'].cursor()
- # 其中'default'是 django 数据库配置的 default, 也可取别的值
- cursor.execute("""SELECT * from auth_user where id = %s""", [1])
- row = cursor.fetchone()
- 类的字段和参数
- 字段: 字符串, 数字, 时间, 二进制
- AutoField(Field) -> 自定义自增列(必须加 primary_key=True)
- IntegerField(Field) -> 整数列
- BooleanField(Field) -> 布尔
- GenericIPAddressField(Field) -> IP 验证(仅限 django admin)
- URLField(CharField) -> url 验证(仅限 django admin)
- # Django 里有很多的字段类型在数据库中都是 Char 类型, 只是用于 django admin 便于区分
- 更多详见: 武沛齐的博客 - Django
- 字段的参数:
- null -> db 中是否可以为空
- default='' -> 默认值
- primary_key -> 是否主键
- db_column -> 列名
- db_index -> 是否可索引
- unique -> 是否可唯一索引
- unique_for_date -> [日期] 部分是否可索引
- unique_for_month -> [月] 部分是否可索引
- unique_for_year -> [年] 部分是否可索引
- auto_now_add -> 创建时, 自动生成时间
- auto_now -> 更新时, 自动更新为当前时间
- # update 方式不生效, 先获取再更改才生效
- ctime = models.DateTimeField(auto_now_add=True)
- UserGroup.objects.filter(id=1).update(caption='CEO') -> 不生效
- obj = UserGroup.objects.filter(id=1).first()
- obj.caption = "CEO" -> 生效, 自动更新更改时间
- obj.save()
- # django admin 中才生效的字段
- blank -> django admin 是否可以为空
- verbose_name='' -> django admin 显示字段中文
- editable -> django admin 是否可以被编辑
- help_text -> django admin 帮助提示
- choices=[] -> django admin 中显示下拉框
- # 可避免连表查询, 提高效率, 一般用于基本不变的选项
- user_type_choices = (
- (1, '超级用户'),
- (2, '普通用户'),
- (3, '普普通用户'),
- )
- user_type_id = models.IntegerField(choices=user_type_choices,default=1)
- error_messages -> 自定义错误信息(字典类型)
- # 字典的键: null, blank, invalid, invalid_choice, unique, unique_for_date
- # 例: error_messages = {'null': "不能为空", 'invalid': '格式错误'}
- validators -> django form , 自定义错误信息(列表类型)
- # 例:
- from django.core.validators import RegexValidator
- from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
- MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
- 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'), ]
- 更多错误信息的使用方法参考武沛齐 - FORM
- 创建 Django admin 用户: python manage.py createsuperuser
- Meta 元信息
- class UserInfo(models.Model):
- ...
- class Meta:
- # 定义数据库中生成的表名称 默认 app 名称 + 下划线 + 类名
- db_table = "table_name"
- # 联合索引
- index_together = [("pub_date", "deadline"),]
- # 联合唯一索引, 一旦三者都相同, 则会被 Django 拒绝创建.
- 可以同时设置多组约束. 为了方便, 对于只有一组约束的情况下, 可以简单地使用一维元素
- unique_together = (("driver", "restaurant"),)
- # admin 后台中显示的表名称
- verbose_name = '用户信息'
- # verbose_name 加 s, 复数形式, 不指定自动加 s
- verbose_name_plural =
- # 默认排序
- ordering=['-order_date'] # 按订单降序排列,- 表示降序, 不加升序, 加? 表示随机
- ordering=['-pub_date','author'] # 以 pub_date 为降序, 再以 author 升序排列
- 更多:
- Admin 拓展知识
- 触发 Model 中的验证和错误提示有两种方式:
- a. Django Admin 中的错误信息会优先根据 Admin 内部的 ModelForm 错误信息提示, 如果都成功, 才来检查 Model 的字段并显示指定错误信息
- b. 调用 Model 对象的 clean_fields 方法, 如:
- # models.py
- class UserInfo(models.Model):
- username = models.CharField(max_length=32)
- email = models.EmailField(error_messages={'invalid': '格式错了.'})
- # views.py
- def index(request):
- obj = models.UserInfo(username='11234', email='uu')
- try:
- print(obj.clean_fields())
- except Exception as e:
- print(e)
- return HttpResponse('ok')
- # Model 的 clean 方法是一个钩子, 可用于定制操作, 如: 上述的异常处理.
- Admin 中修改错误提示
- # admin.py
- from django.contrib import admin
- from model_club import models
- from django import forms
- class UserInfoForm(forms.ModelForm):
- username = forms.CharField(error_messages={'required': '用户名不能为空.'})
- email = forms.EmailField(error_messages={'invalid': '邮箱格式错误.'})
- age = forms.IntegerField(initial=1, error_messages={'required': '请输入数值.', 'invalid': '年龄必须为数值.'})
- class Meta:
- model = models.UserInfo
- # fields = ('username',)
- fields = "__all__"
- class UserInfoAdmin(admin.ModelAdmin):
- form = UserInfoForm
- admin.site.register(models.UserInfo, UserInfoAdmin)
- ORM 连表的几种类型
- ORM 一对多
- 当一张表中创建一行数据时, 有一个单选的下拉框(可以被重复选择)
- 创建表结构时关联外键
- user_group = models.ForeignKey("UserGroup",to_field='uid') ->> obj(UserGroup)
- # 自动创建 user_group_id 列, 存的是数字(关联主键)
- 添加数据时关联 id 或对象
- 方式一: 创建数据时添加 id 关联
- models.UserInfo.object.create(name='root', user_group_id=1)
- 方式二: 查询 obj 对象进行关联
- user_group = models.UserGroup.objects.filter(id=1).first()
- 一对多自关联
- 由原来的 2 张表, 变成一张表!
- # 例: 回复评论
- class Comment(models.Model):
- news_id = models.IntegerField() -> 新闻 ID
- content = models.CharField(max_length=32) -> 评论内容
- user = models.CharField(max_length=32) -> 评论者
- reply = models.ForeignKey('Comment',null=True,blank=True,related_name='xxxx') -> 回复 ID
- # 注意: 回复的 id 必须是已经存在的评论的 id
- ORM 多对多
- 在某表中创建一行数据是, 有一个可以多选的下拉框
- 两种创建方式
- 以下两种创建方式建议都用, 自动创建的只能关联两个表, 自定义的可以不断关联
- 方式一: 自定义关系表
- 可以直接操作第三张表, 但无法通过字段跨表查询, 查询麻烦
- class UserInfo(models.Model):
- ...
- class UserGroup(models.Model):
- ...
- # 创建中间表
- class UserInfoToUserGroup(models.Model):
- user_info_obj = models.ForeignKey(to='UserInfo',to_field='nid')
- group_obj = models.ForeignKey(to='UserGroup',to_field='id')
- # 添加关联数据:
- UserInfoToApp.objects.create(user_info_obj_id=1,group_obj_id=2)
- 方式二: Django 自动创建关系表
- 可以使用字段跨表查询, 但无法直接操作第三张表
- class UserInfo(models.Model):
- ...
- # ManyToManyField 字段
- class UserGroup(models.Model):
- user_info = models.ManyToManyField("UserInfo")
- 方式三: 既自定义第三张关系表 也使用 ManyToManyField 字段(杂交类型)
- 既可以使用字段跨表查询, 也可以直接操作第 3 张关系表
- 注意: obj.m.all() 只有查询和清空方法
- # 例: 博主粉丝关系
- class UserInfo(AbstractUser):
- ...
- fans = models.ManyToManyField(to='UserInfo',
- through='UserFans', -> 指定关系表表名
- through_fields=('user', 'follower')) -> 指定关系表字段
- class UserFans(models.Model):
- ...
- user = models.ForeignKey(to='UserInfo', to_field='nid', related_name='users')
- follower = models.ForeignKey(to='UserInfo', to_field='nid', related_name='followers')
- class Meta:
- unique_together = [('user', 'follower'),]
- 多对多自关联
- (由原来的 3 张表, 变成只有 2 张表)
- 把两张表通过 choices 字段合并为一张表
- 使用 ManyToManyField 字段
- 1, 查询第三张关系表前面那一列: obj.m
- 2, 查询第三张关系表后面那一列: obj.userinfo_set
- class Userinfo(models.Model):
- sex=((1,'男'),(2,'女'))
- gender=models.IntegerField(choices=sex)
- m=models.ManyToManyField('Userinfo')
- # 通过男士查询女生
- boy_obj=models.Userinfo.objects.filter(id=4).first()
- res=boy_obj.m.all()
- # 通过女士查询男生
- girl_obj=models.Userinfo.objects.filter(id=4).first()
- res=girl_obj.userinfo_set.all()
- ORM 一对一
- 在某表中创建一行数据时, 有一个单选的下拉框(下拉框中的内容被用过一次就消失了)
- 例如: 原有含 10 列数据的一张表保存相关信息, 经过一段时间之后, 10 列无法满足需求, 需要为原来的表再添加 5 列数据
- r = models.OneToOneField(...)
- # 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)
- ORM 连表操作
- 字段参数
- 一对多 ForeignKey()
- to -> 要关联的表名
- to_field='uid', -> 要关联的字段, 不写默认关联主键
- 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(func)
- def func():
- return 10
- class MyModel(models.Model):
- user = models.ForeignKey(...,on_delete=models.SET(func))
- related_name=None, -> 反向操作时, 使用的字段名, 用于替换[表名_set]
- 如: obj. 表名_set.all()
- related_query_name=None, -> 反向操作时, 使用的连接前缀, 用于替换[表名]
- 如: ...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 中是否显示关联数据
- 多对多 ManyToManyField()
- symmetrical=None, -> 仅用于多对多自关联时, 指定内部是否创建反向操作的字段
- => 做如下操作时, 不同的 symmetrical 会有不同的可选字段
- models.BB.objects.filter(...)
- => 可选字段有: code, id, m1
- class BB(models.Model):
- code = models.CharField(max_length=12)
- m1 = models.ManyToManyField('self',symmetrical=True)
- => 可选字段有: code, id, m1, bb
- class BB(models.Model):
- code = models.CharField(max_length=12)
- m1 = models.ManyToManyField('self',symmetrical=False)
- through=None, -> 自定义第三张表时, 用于指定关系表
- through_fields=None, -> 自定义第三张表时, 用于指定关系表中哪些字段做多对多关系表
- db_constraint=True, -> 是否在数据库中创建外键约束
- db_table=None, -> 默认创建第三张表时, 数据库中表的名称
- 一对一 OneToOneField()
- to -> 要关联的表名
- to_field='uid', -> 要关联的字段, 不写默认关联主键
- on_delete=None, -> 删除关联表中的数据时, 当前表与其关联的行的行为
- 跨表查询(神奇的双下划线 2)
- 获取值时使用 . 连接
- group_obj = models.UserGroup.objects.filter(id=1).first() # orm 连表必须取单个对象
- # 增
- group_obj.user_info.add(1) -> 添加一个
- group_obj.user_info.add(2,3,4) -> 添加多个
- group_obj.user_info.add(*[1,2,3,4]) -> 添加 * 列表
- # 删
- group_obj.user_info.remove(1)
- group_obj.user_info.remove(2,4)
- group_obj.user_info.remove(*[1,2,3])
- group_obj.user_info.clear() -> 清除当前对象关联的多对多数据
- # 改
- group_obj.user_info.set([3,5,7]) -> (不加 *)只保留 1-3,1-5,1-7, 其它删除
- # 查
- group_obj.user_info.all() -> 获取所有相关的主机 obj 的 QuerySet
- group_obj.user_info.filter()
- ......
- 搜索条件使用 __ 连接 (value,value_list,fifter)
- obj = models.UserGroup.objects.filter(id=1).value('name','user_info__name').first()
- 在 html 里也用 obj.user_group__name
- 反查
- # . 操作, 获取对象的 QuerySet, 表名小写_set
- user_info_obj.usergroup_set.add(group_obj)
- user_info_obj.usergroup_set.remove(group_obj)
- user_info_obj.usergroup_set.all()
- user_info_obj.usergroup_set.filter()
- ......
- # __操作, 搜索属性, 表名小写__属性
- obj = models.UserInfo.objects.filter('usergruop__name').first()
- 设置反向查找别名
- related_query_name -> 反向查找时用 obj. 别名_set.all(), 保留了_set
- relatedname -> 反向查找时用 obj. 别名. all()
- # 例如:
- '''把男女表混合在一起, 在代码层面控制第三张关系表的外键关系'''
- # models.py
- class UserInfo(models.Model):
- ...
- sex=((1,'男'),(2,'女'))
- gender=models.IntegerField(choices=sex)
- class U2U(models.Model):
- b=models.ForeignKey(Userinfo,related_name='boy')
- g=models.ForeignKey(Userinfo,related_name='girl')
- # 写到此处问题就来了, 原来两个外键 对应 2 张表 2 个主键, 可以识别男女
- # 现在两个外键对应 1 张表, 反向查找, 无法区分男女了了
- # object 对象女. U2U.Userinfo.set object 对象男. U2U.Userinfo.set
- # 所以要加 related_name 设置反向查找命名 对表中主键加以区分
- # 查找方法
- # 男: obj.a.all()
- # 女: obj.b.all()
- # views.py
- def index(request):
- #查找 ID 为 1 男孩 相关的女孩
- boy_obj=models.UserInfo.objects.filter(id=1).first()
- res = boy_obj.boy.all() # 得到 U2U 的对象再正向跨表
- for obj in res:
- print(obj.girl.name)
- return HttpResponse('OK')
- 分组和聚合查询
- aggregate() 聚合函数
- 通过对 QuerySet 进行计算, 返回一个聚合值的字典.
- aggregate()中每一个参数都指定一个包含在字典中的返回值. 即在查询集上生成聚合.
- from django.db.models import Avg,Sum,Max,Min
- # 求书籍的平均价
- ret = models.Book.objects.all().aggregate(Avg('price'))
- # {'price__avg': 145.23076923076923}
- # 参与西游记著作的作者中最老的一位作者
- ret = models.Book.objects.filter(title__icontains='西游记').values('author__age').aggregate(Max('author__age'))
- # {'author__age__max': 518}
- annotate() 分组函数
- # 查看每一位作者出过的书中最贵的一本
- # (按作者名分组 values(), 然后 annotate() 分别取每人出过的书价格最高的)
- ret=models.Book.objects.values('author__name').annotate(Max('price'))
- # <QuerySet[
- # {'author__name': '吴承恩', 'price__max': Decimal('234.000')},
- # {'author__name': '吕不韦','price__max': Decimal('234.000')},
- # {'author__name': '姜子牙', 'price__max': Decimal('123.000')},
- # ]>
- 浅谈 ORM 查询性能
- 普通跨表查询
- obj_list=models.Love.objects.all()
- for row in obj_list: # for 循环 10 次发送 10 次数据库查询请求
- print(row.b.name)
- 原理: 第一次发送查询请求, 每 for 循环一次也会发送查询请求
- select_related
- 结果为对象, query_set 类型的对象都有该方法
- 原理: select_related 查询时主动完成连表形成一张大表, for 循环时不用额外发请求
- 试用场景: 节省硬盘空间, 数据量少的时候适用, 相当于做了一次数据库查询;
- obj_list=models.Love.objects.all().select_related('b') # 查询时关联 b 表
- for row in obj_list:
- print(row.b.name)
- prefetch_related:
- 结果为对象
- 原理: select_related 虽好, 但是做连表操作依然会影响查询性能, prefetch_related 不做连表, 多次单表查询外键表, 去重之后显示, 2 次单表查询(有 N 个外键做 1+N 次单表查询)
- 适用场景: 效率高, 数据量大的时候使用
- obj_list=models.Love.objects.all().prefetch_related('b')
- for obj in obj_list:
- print(obj.b.name)
- update()和对象. save()修改方式的性能 PK
- # 方式 1
- models.Book.objects.filter(id=1).update(price=3)
- # 执行结果
- (0.000) BEGIN; args=None
- (0.000) UPDATE "app01_book" SET "price" = '3.000' WHERE "app01_book"."id" = 1; args=('3.000', 1)
- # 方式 2
- book_obj=models.Book.objects.get(id=1)
- book_obj.price=5
- book_obj.save()
- # 执行结果
- (0.000) SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", "app01_book"."date", "app01_book"."publish_id", "app01_book"."classify_id" FROM "app01_book" WHERE "app01_book"."id" = 1; args=(1,)
- (0.000) BEGIN; args=None
- (0.000) UPDATE "app01_book" SET "title" = '我的奋斗', "price" = '5.000', "date" = '1370-09-09', "publish_id" = 4, "classify_id" = 3 WHERE "app01_book"."id" = 1; args=('我的奋斗', '5.000', '1370-09-09', 4, 3, 1)
- # 结论:
- update() 比 obj.save()性能好
- Django 自带 ContentType 表
- Django 程序启动后自带的一张表, 记录了 Django 程序的所有 APP 下 model 中的表名和所在 app 的名称
- 通过 ContentType 中的 app 名和表名, 查找到 Django model 中所有表;
- from django.contrib.contenttypes.models import ContentType
- def test(request):
- c = ContentType.objects.get(app_label='app01',model='boy')
- print(c) -> boy
- print(c.model_class()) -> app01.models.Boy
- 解决 1 张表 同时与其他 N 张表建立外键, 并且多个外键中只能选择 1 个的复杂问题
- 场景 1: 现有 N 种优惠券, 每 1 种优惠券分别对应 N 门课程中的一门课程, 怎么设计表结构呢?
- 场景 2: 学生的学习成绩如何奖惩, 作业如何奖惩, 学习进度如何奖惩...
- # 例: 场景 1
- from django.db import models
- from django.contrib.contenttypes.models import ContentType
- from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
- class DegreeCourse(models.Model):
- name = models.CharField(max_length=128, unique=True)
- # GenericRelation 自动连表查询
- xxx = GenericRelation('Coupon')
- class Course(models.Model):
- name = models.CharField(max_length=128, unique=True)
- class Coupon(models.Model):
- """ 优惠券生成规则
- ID 优惠券名称 content_type_id(表) object_id(表中数据 ID)
- 1 通用 null null
- 2 满 100-10 8 1
- 3 满 200-30 8 2
- 4 满 200-30 9 1
- """ name = models.CharField(max_length=64, verbose_name=" 活动名称 ")
- # course_type 代指哪张表 注意该字段必须为 content_type
- content_type = models.ForeignKey(ContentType,blank=True,null=True)
- # 代指对象 ID 该字段必须为 object_id
- object_id = models.PositiveIntegerField(blank=True, null=True, help_text="可以把优惠券跟课程绑定")
- # GenericForeignKey 通过 content_type 直接创建外键关系, 不会生成额外的列
- content_object = GenericForeignKey('content_type','object_id')
- # 给学位课 1, 创建优惠券 100
- # 方式 1:
- # 1, 在学位课表中 , 找到学位课 1
- d1 = models.DegreeCourse.objects.get(id=1)
- # 2, 在 ContentType 找到学位课表
- c1 = ContentType.objects.get(app_label='app01',model='degreecourse')
- # 3, 给学位课 1, 创建优惠券 100
- models.Coupon.objects.create(name='优惠券',brief='100',content_type=c1,object_id=d1.id)
- # 方式 2:
- d1 = models.DegreeCourse.objects.get(id=1)
- models.Coupon.objects.create(name='优惠券',brief='100',content_object=d1)
- # 查询关联的所有优惠券
- d1 = models.DegreeCourse.objects.get(id=1)
- print(d1.xxx.all())
- v = models.DegreeCourse.objects.values('name','xxx__brief','xxx__name')
- print(v)
其他小技巧
数据库表删除重建:
先到数据库把表删掉: drop table
注释 django 中对应的 Model
执行以下命令:
python manage.py makemigrations
python manage.py migrate --fake -> 只记录变化, 不提交数据库操作
去掉注释重新迁移
- python manage.py makemigrations
- python manage.py migrate
字典 key 替换
- # 把 value 传给新 key 并同时删除旧 key
- row['delivery'] = [row.pop('投递')]
获取字段名和 verbose_name
- fields_data = Group._meta.fields
- for key in data:
- # 这里是将当前的数据转换成数据字典, 方便后面修改后提交
- data_dict = Group.__dict__
- for field in fields_data:
- # 这样或输出这条记录的所有字段名, 需要的话还可以输出 verbose_name
- print(field.name)
- if field.name == key:
- #进行匹配, 将前端传来的字段匹配到, 然后修改数据库里面的数据
- data_dict[key] = data[key]
- # 保存数据到数据库, 这样的好处就是提高效率, 避免过多重复操作
参考博客
ORM 详细讲解
武沛齐的博客 - Django
来源: https://www.cnblogs.com/JeromeLong/p/9875171.html