ORM 模型
ORM 概念
对象关系映射 全称(Object Relational Mapping) 简称 ORM 模式是一种为了解决面向对象与关系型数据库存在的互不匹配的现象的技术.
简单来说, ORM 是通过使用描述对象和数据库之间映射的元数据, 将程序中的对象自动持久化到关系数据库中
ORM 在业务逻辑层面和数据库之间充当了桥梁的作用
ORM 由来
让我们从 O/R 开始. 字母 O 起源于 "对象"(Object), 而 R 则来自于 "关系"(Relational).
几乎所有的软件开发过程中都会涉及到对象和关系数据库. 在用户层面和业务逻辑层面, 我们是面向对象的. 当对象的信息发生变化的时候, 我们就需要把对象的信息保存在关系数据库中.
按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多 SQL 语句用来增加, 读取, 修改, 删除相关数据, 而这些代码通常都是极其相似或者重复的.
ORM 的优势
ORM 解决的主要问题是对象和关系的映射. 它通常将一个类和一张表一一对应, 类的每个实例对应表中的一条记录, 类的每个属性对应表中的每个字段.
ORM 提供了对数据库的映射, 不用直接编写 SQL 代码, 只需操作对象就能对数据库操作数据.
让软件开发人员专注于业务逻辑的处理, 提高了开发效率.
ORM 的劣势
ORM 的缺点是会在一定程度上牺牲程序的执行效率.
ORM 的操作是有限的, 也就是 ORM 定义好的操作是可以完成的, 一些复杂的查询操作是完成不了.
ORM 用多了 SQL 语句就不会写了, 关系数据库相关技能退化...
ORM 总结
ORM 只是一种工具, 工具确实能解决一些重复, 简单的劳动. 这是不可否认的.
但我们不能指望某个工具能一劳永逸地解决所有问题, 一些特殊问题还是需要特殊处理的.
但是在整个软件开发过程中需要特殊处理的情况应该都是很少的, 否则所谓的工具也就失去了它存在的意义.
Django 中的 ORM
Django 项目使用 MySQL 数据库
在 Django 项目的 settings.py 文件中, 配置数据库连接信息:
- DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': 'bookmanager',
- 'HOST': '127.0.0.1',
- 'PORT': 3306,
- 'USER': 'root',
- 'PASSWORD': "123456"
- }
- }
在与 Django 项目同名的目录下的__init__.py 文件中写如下代码, 告诉 Django 使用 pymysql 模块连接 MySQL 数据库:
- import pymysql
- pymysql.install_as_MySQLdb()
注: 数据库迁移的时候出现一个警告
原因是因为: 数据库没有设置严格模式
- WARNINGS:
- ?: (MySQL.W002) MySQL Strict Mode is not set for database connection 'default'
- HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it.
在配置中多加一个 OPTIONS 参数: Django 官网解释
- 'OPTIONS': {
- 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"},
- Model
在 Django 中 model 是你数据的单一, 明确的信息来源. 它包含了你存储的数据的重要字段和行为. 通常, 一个模型 (model) 映射到一个数据库表.
基本情况:
每个模型都是一个 Python 类, 它是 django.db.models.Model 的子类.
模型的每个属性都代表一个数据库字段.
综上所述, Django 为您提供了一个自动生成的数据库访问 API, 详询官方文档链接.
快速入门
下面这个例子定义了一个 Person 模型, 包含 first_name 和 last_name.
- from django.db import models
- class Person(models.Model):
- first_name = models.CharField(max_length=30)
- last_name = models.CharField(max_length=30)
first_name 和 last_name 是模型的字段. 每个字段被指定为一个类属性, 每个属性映射到一个数据库列.
上面的 Person 模型将会像这样创建一个数据库表:
- CREATE TABLE myapp_person (
- "id" serial NOT NULL PRIMARY KEY,
- "first_name" varchar(30) NOT NULL,
- "last_name" varchar(30) NOT NULL
- );
一些说明:
表 myapp_person 的名称是自动生成的, 如果你要自定义表名, 需要在 model 的 Meta 类中指定 db_table 参数, 强烈建议使用小写表名, 特别是使用 MySQL 作为数据库时.
id 字段是自动添加的, 如果你想要指定自定义主键, 只需在其中一个字段中指定 primary_key=True 即可. 如果 Django 发现你已经明确地设置了 Field.primary_key, 它将不会添加自动 ID 列.
本示例中的 CREATE TABLE SQL 使用 PostgreSQL 语法进行格式化, 但值得注意的是, Django 会根据配置文件中指定的数据库类型来生成相应的 SQL 语句.
Django 支持 MySQL5.5 及更高版本.
字段
常用字段
AutoField
自增的整形字段, 必填参数 primary_key=True, 则成为数据库的主键. 无该字段时, django 自动创建.
一个 model 不能有两个 AutoField 字段.
IntegerField
一个整数类型. 数值的范围是 -2147483648 ~ 2147483647.
CharField
字符类型, 必须提供 max_length 参数. max_length 表示字符的长度.
DateField
日期类型, 日期格式为 YYYY-MM-DD, 相当于 Python 中的 datetime.date 的实例.
参数:
auto_now: 每次修改时修改为当前日期时间.
auto_now_add: 新创建对象时自动添加当前日期时间.
auto_now 和 auto_now_add 和 default 参数是互斥的, 不能同时设置.
DatetimeField
日期时间字段, 格式为 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ], 相当于 Python 中的 datetime.datetime 的实例.
DecimalField
10 进制小数
max_digits, 小数总长度
decimal_places, 小数位长度
TextField(Field)
文本类型
字段类型, 详情可点击查询官网.
字段类型
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
TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field)
- 长整数, 时间间隔, 数据库中按照 bigint 存储, ORM 中获取的值为 datetime.timedelta 类型
FloatField(Field)
- 浮点型
DecimalField(Field)
- 10 进制小数
- 参数:
max_digits, 小数总长度
decimal_places, 小数位长度
BinaryField(Field)
- 二进制类型
示例
- class User(models.Model):
- uid = models.AutoField(primary_key=True)
- name = models.CharField(verbose_name='用户名',max_length=32,db_column='username',unique=True) # varchar(32)
- age = models.IntegerField(verbose_name='年龄',blank=True, null=True) # 整数
- bitrh = models.DateTimeField(verbose_name='生日',auto_now=True)
- # auto_now_add=True 新增数据时保存当前的时间
- # auto_now=True 新增和编辑数据时保存当前的时间
- phone = MyCharField(11,verbose_name='手机号')
- gender = models.BooleanField(choices=((True,'男'),(False,'女')))
- # 外键
- class Book(models.Model):
- title = models.CharField(max_length=32)
- pub = models.ForeignKey('Publisher', on_delete=models.CASCADE)
- # CASCADE 级连删除 on_delete 2.0 版本后是必填项
- # SET
- # SET_DEFAULT
- # SET_NULL null=True
- # DO_NOTHING
- # PROTECT
自定义字段
Django 字段与数据库字段类型的对应关系
- 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)',
自定义 char 类型字段
- class MyCharField(models.Field):
- """
- 自定义的 char 类型的字段类
- """
- def __init__(self, max_length, *args, **kwargs):
- self.max_length = max_length
- super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
- def db_type(self, connection):
- """
- 限定生成数据库表的字段类型为 char, 长度为 max_length 指定的值
- """ return'char(%s)' % self.max_length
使用自定义 char 类型字段:
- class User(models.Model):
- uid = models.AutoField(primary_key=True)
- # 使用自定义的 char 类型的字段
- phone = MyCharField(11,verbose_name='手机号')
字段参数
max_length 指定长度
null True 数据库可以为空
blank True 用户可以输入为空(form)
verbose_name 提示的中文
unique 唯一约束
db_index 索引
default 默认值
choices 可以选择的内容 ((1,'xxx'),())
db_column 列名
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'), ]
- )
Model Meta 参数
- class UserInfo(models.Model):
- nid = models.AutoField(primary_key=True)
- username = models.CharField(max_length=32)
- class Meta:
- # 数据库中生成的表名称 默认 App 名称 + 下划线 + 类名
- db_table = "table_name"
- # admin 中显示的表名称
- verbose_name = '个人信息'
- # verbose_name 加 s
- verbose_name_plural = '所有用户信息'
- # 联合索引
- index_together = [
- ("pub_date", "deadline"), # 应为两个存在的字段
- ]
- # 联合唯一索引
- unique_together = (("driver", "restaurant"),) # 应为两个存在的字段
必会的 13 中方法
- import os
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bookmanager.settings")
- import django
- django.setup()
- from app02 import models
- # all() 获取所有的数据 QuerySet [ 对象, 对象]
- ret = models.User.objects.all()
- # get() 获取一条数据 对象 查询不到或者多个的时候就报错
- ret = models.User.objects.get(pk=1)
- # filter() 获取满足条件的所有对象 QuerySet [ 对象, 对象]
- ret = models.User.objects.filter(pk=1)
- # exclude() 获取不满足条件的所有对象 QuerySet [ 对象, 对象]
- ret = models.User.objects.exclude(pk=1)
- # order_by() 排序 QuerySet [ 对象, 对象] 默认升序 降序给字段前 + 负号
- ret = models.User.objects.order_by('age','uid')
- # reverse() 反转 QuerySet [ 对象, 对象] 对已经排序的 QuerySet 生效
- ret = models.User.objects.all().order_by('uid').reverse()
- # values() 不加参数 获取所有的字段名和值 QuerySet [ 字典, 字典]
- # values('uid','name') 指定参数 获取指定字段的名和值
- ret = models.User.objects.all().values('uid','name')
- # for i in ret:
- # # print(i)
- # # print(i['uid'],i['name']) # i.uid i.name
- # values_list() 不加参数 获取所有的字段值 QuerySet [ (),()]
- # values_list('name','uid') 指定参数 获取指定字段的值
- ret = models.User.objects.all().values_list('name','uid')
- # for i in ret:
- # print(i)
- # distinct 去重
- ret = models.User.objects.all().values('age').distinct()
- # first 取第一个元素 没有的话就是 None
- ret = models.User.objects.filter(name='alex',age=85).first()
- # last 取最后一个元素 没有的话就是 None
- ret = models.User.objects.filter(name='alex',age=85).last()
- # count 计数 len() __len__ count 比 len 效率高
- ret = models.User.objects.all().count()
- # exists 判断查询结果是否存在
- ret = models.User.objects.filter(name='alex',age=84).exists()
- print(ret)
- """
- 返回是 QuerySet
- all
- filter
- exclude
- order_by
- reverse
- values
- values_list
- distinct
- 返回对象
- get
- first
- last
- 返回数字
- count
- 返回布尔值
- exists
- """
单表查询双下划线
- ret = models.User.objects.filter(pk=2)
- ret = models.User.objects.filter(pk__gt=2) # greater than 大于
- ret = models.User.objects.filter(pk__gte=2) # greater than equal 大于等于
- ret = models.User.objects.filter(pk__lt=2) # Less than 小于
- ret = models.User.objects.filter(pk__lte=2) # Less than equal 小于等于
- ret = models.User.objects.filter(pk__in=[1,3]) # 成员判断
- ret = models.User.objects.filter(pk__range=[1,3]) # 范围
- ret = models.User.objects.filter(name__contains='alex') # like
- ret = models.User.objects.filter(name__icontains='alex') # 忽略大小写 包含
- ret = models.User.objects.filter(name__startswith='alex') # 以某某开头
- ret = models.User.objects.filter(name__istartswith='alex') # 以某某开头 忽略大小写
- ret = models.User.objects.filter(name__endswith='alex') # 以某某开头
- ret = models.User.objects.filter(name__iendswith='alex') # 以某某开头 忽略大小写
- ret = models.User.objects.filter(age__isnull=False) # name 字段是否为 null
- ret = models.User.objects.filter(bitrh__year='2020') # year
- ret = models.User.objects.filter(bitrh__contains='2020-11-17')
多表关系和参数
属于 sql 中 多表关联的意思, 例如书与作者的关系, 一本书可以对应多个作者, 多个作者亦可以代表一本书, 属于多对多关系, 在 django 中 有方法设置这种情况
- 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",
- to_field="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 中是否显示关联数据
- 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)
- 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 Book(models.Model):
- title = models.CharField(max_length=32)
- class Author(models.Model):
- name = models.CharField(max_length=32)
- books = models.ManyToManyField('Book') # 不会生成字段, 但会生成第三张表
执行 makemigrations 生成配置文件
- PS mysite1> python .\manage.py makemigrations app01
- Migrations for 'app01':
app01\migrations\0004_author.py
- Create model Author
查看 migrate 执行后的作用 可以使用 sqlmigrate 执行 查看
- PS mysite1> python .\manage.py sqlmigrate app01 0004 #app01 程序 1/ 0004 表示 migrations 中的文件名
- # 上面的例子也就相当于执行了下例 sql
- BEGIN;
- --
- -- Create model Author
- --
- CREATE TABLE `app01_author` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(32) NOT NULL);
- CREATE TABLE `app01_author_books` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `author_id` integer NOT NULL, `book_id` integer NOT NULL);
- ALTER TABLE `app01_author_books` ADD CONSTRAINT `app01_author_books_author_id_a9f734c0_fk_app01_author_id` FOREIGN KEY (`author_id`) REFERENCES `app01_author` (`id`);
- ALTER TABLE `app01_author_books` ADD CONSTRAINT `app01_author_books_book_id_e6c2060a_fk_app01_book_id` FOREIGN KEY (`book_id`) REFERENCES `app01_book` (`id`);
- ALTER TABLE `app01_author_books` ADD CONSTRAINT `app01_author_books_author_id_book_id_9eb5e4e7_uniq` UNIQUE (`author_id`, `book_id`);
- COMMIT;
导入数据库
PS C:\Application\oldboyLearnProject\oldbaoy_python_project\day16 \ 课上 \ mysite1> python .\manage.py migrate
- Operations to perform:
- Apply all migrations: admin, app01, app02, auth, contenttypes, sessions
- Running migrations:
- Applying app01.0004_author... OK
- Applying app02.0001_initial... OK
视图添加
- # urls.py
- url(r'^author/$',views.author_list,name='author'),
- # app01/views.py
- def author_list(request):
- all_authors = models.Author.objects.all()
- # for author in all_authors:
- # print(author.pk)
- # print(author.name)
- # print(author.books,type(author.books)) # 关系管理对象
- # print(author.books.all(),type(author.books)) # 关系的所有对象
- return render(request,'author.html',{'all_authors':all_authors})
- class AuthorEdit(View):
- def get(self,request,*args,**kwargs):
- author = models.Author.objects.get(pk=kwargs['pk'])
- books = models.Book.objects.all()
- return render(request,'author_edit.html',{'author':author,'books':books})
- def post(self,request,*args,**kwargs):
- # 根据 pk 查找对象
- author = models.Author.objects.get(pk=kwargs['pk'])
- # 获取 post 请求提交的作者名称, bookid
- author_name = request.POST.get('author')
- book_id = request.POST.getlist('book')
- # 在内存中替换原作者 并且提交到数据库
- author.name = author_name
- author.save()
- # 设置关联
- author.books.set(book_id)
- return redirect(reverse('author'))
- #### 多对多关系的增删改查
创建多对多关系表
- class Author(models.Model):
- name = models.CharField(max_length=32)
- books = models.ManyToManyField('Book') # 不会生成字段, 但会生成第三张表
查询
- author_obj.name
- author_obj.books # 关系管理对象, 还不能操作
- author_obj.all() # 关联的所有书籍对象 对象列表
- book_obj.pub # 出版社的对象
新增
- models.Author.objects.create(name='小强') # 创建一条新数据
- author_obj.books.set(book_id) # 设置多对多关系 在第三张表中创建多对多映射关系 ['3','4'] [对象, 对象]
- author_obj.books.set(models.Author.objects.filter(pk__in=book_id)) # 设置多对多关系 方法二
编辑(修改)
- author = models.Author.objects.get(name="小强")
- # 在内存中替换原作者 并且提交到数据库
- author.name = 'new_name'
- author.save()
- # 设置多对多关联
- author.books.set(['id','id'])
- author.books.set([对象, 对象])
删除
- # 找到对象进行删除 也可以用 query_set 列表进行删除
- Author.objects.filter(name='小强').delete()
- Author.objects.filter(name='小强').first().delete()
外键的操作(正反向查询操作)
基于对象的查询
正向查询
从多的一方 往 少的一方拿
语法: 对象. 关联字段. 字段
- book_obj = models.Book.objects.get(pk=6)
- print(book_obj.pub) # 外键关联字段 可直接拿到 关联的对象
反向查询
从少的一方 往 多的一方拿
语法:
表名_set 关系管理对象 (django 封装的所以_set)
表名_set.all() 关系所有的对象
- pub_obj = models.Publisher.objects.get(pk=8)
- print(pub_obj.book_set,type(pub_obj.book_set)) # 关系管理对象
- print(pub_obj.book_set.all()) # 关联的所有对象
注: 如果在设置外键的字段中加入了 related_name = '自定义名称', 则使用表名_set 进行反向查询会报错, 设置了该别名后应当使用自定义名称来进行反向查询
- # related_name = 'books'
- print(pub_obj.books) # 关联的所有对象
- print(pub_obj.books.all()) # 关联的所有对象
基于字段的查询
正向查询(利用书查找出版社)
语法:
pub__name 代表: pub 外键字段 __name 取到了外键所关联的对象的 name 字段 (连表查询)
ret = models.Book.objects.filter(pub__name='修仙出版社')
反向查询
跟使用外键查询 相反 (利用出版社查找到书)
- # models.py
- class Book(models.Model):
- title = models.CharField(max_length=32)
- pub = models.ForeignKey('Publisher',
- on_delete=models.CASCADE,
- related_name='books', # 反向查询对象时 可以替代 表名_set 的设置
- related_query_name='book', # 反向查询字段时 可以替代 表名__字段 的设置
- )
不指定 related_name='books' 使用表名__字段查询
ret = models.Publisher.objects.filter(book__title='合体修炼秘籍')
指定 related_name='books' 使用 related_name__字段名 查询
ret = models.Publisher.objects.filter(books__title='合体修炼秘籍')
根据字段查询查询到的是一个 对象, 但是如果觉得 books__title 别扭, 想代表对应的名称
可以使用 related_query_name = 'book'自定义来设置想用的名称
ret = models.Publisher.objects.filter(book__title='合体修炼秘籍')
注: 三种方法使用一种即可, 没有必要都使用
多对多的操作(正反向查询操作)
正向查询
- author_obj = models.Author.objects.get(name='马海阳')
- print(author_obj.books.all())
反向查询(和外键的正反查询一样)
表名_set 或者 related_name = '自定义名称' 或者 related_query_name = '自定义名称'
- book_obj = models.Book.objects.get(title='大乘期修炼秘籍')
- print(book_obj.author_set.all())
外键和多对多都可以使用的五种方法
外键 方法
注: 外键中使用方法时 需注意没有 ID 方法, 只有对象的才能设置
- # 根据字段反向查询到的书籍对象
- ret = models.Publisher.objects.filter(book__title='大乘期修炼秘籍').first()
- ret = models.Publisher.objects.filter(book__pk=6).first()
- # 查询所有关联的对象
- # .books.all() 为反向查询 根据 related_name= 'books' 默认 则使用 book_set 获得关系管理对象
- # all 方法为关系管理对象的方法
- print(ret.books.all())
- # 设置根据查询条件查找到的所有的书籍的的对象 添加上 ret 所属出版社
- # set 方法默认是修改全部 如果原来有所属该出版社 新 set 时 没有筛选添加, 则会清空 为 none
- # 外键方法 设置只能传参对象 不能使用主键 ID
- ret.books.set(models.Book.objects.filter(pk__in=[6,7,9])) # 只能用对象
- # 删除时 因为外键关联 所以需要给外键字段 设置参数可以为空 才能进行 remove 操作
- # 外键字段加入参数 null=True 重新生成 导入数据库
- ret.books.remove(*models.Book.objects.filter(pk__in=[7,]))
- ret.books.clear()
多对多方法
- # all() 获取所有关联的对象
- print(author_obj.books.all())
- # set() 设置多对多关系 重新设置
- author_obj.books.set([]) # 相当于清空作者与书的关系
- # add() 新增关系
- # 两种方法
- author_obj.books.add(*models.Book.objects.filter(pk__in=[6,7])) # 传入查询到的对象
- author_obj.books.add(6) # 可以传入 ID PK 也可以传入对象
- # remove() 删除
- author_obj.books.remove(6) # 主键为 6 (id)
- author_obj.books.remove(*models.Book.objects.filter(pk__in=[6,7]))
- # clear() 清空所有的关系
- author_obj.books.clear()
- # create() 创建书 并且 添加关系
- # 两步骤: 1. 创建书籍 2. 并且与作者进行添加关系
- author_obj.books.create(title='入门修炼秘籍',pub_id=7)
- # 创建作者的同时 并且关联书
- # book_obj = models.Book.objects.get(title='大乘期修炼秘籍')
- # book_obj.author_set.create(name='渣哥')
聚合函数
相当于 sql 中的聚合函数 对于一些分组以后 数据 数值的整体计算
- # python 中引入聚合函数
- from django.db.models import Max,Min,Count,Avg,Sum
- # 单独聚合 使用 aggregate
- ret = models.Book.objects.aggregate(min=Min('price'),max=Max('price'))
- # 也可以将符合条件的数据过滤出来后进行聚合处理
- ret = models.Book.objects.filter(pk__gte=2).aggregate(min=Min('price'),max=Max('price'))
- print(ret)
分组聚合
分组相当于数据库中的 group 分组, 对一些属于同一个类型的进行整体 处理,
分组配合聚合函数使用, 单独使用分组没有任何意义
注意: values 使用两次会导致结果不符合条件
- # 统计每一本书的作者个数
- ret = models.Book.objects.annotate(Count('author')).values()
- # 统计出每个出版社买的最便宜的书的价格
- # 反向操作
- ret = models.Publisher.objects.annotate(Min('book__price')).values()
- # 正向操作
- ret = models.Book.objects.values('pub__name').annotate(min=Min('price'))
- # 统计不止一个作者的图书
- ret = models.Book.objects.annotate(count=Count('author')).filter(count__gt=1)
- # 查询各个作者出的书的总价格
- # 从作者角度
- ret = models.Author.objects.annotate(Sum('books__price')).values()
- # 相反 从书的角度
- ret = models.Book.objects.values('author__name').annotate(Sum('price'))
- print(ret)
F 查询和 Q 查询
F 查询
Django 提供 F() 来做这样的比较. F() 的实例可以在查询中引用字段, 来比较同一个 model 实例中两个不同字段的值.
# 引入 F 查询 from django.db.models import F # 查询销量大于库存的数据 ret = models.Book.objects.filter(sale__gt=F('stock')) print(ret) # 相当于拿某个字段出来进行操作 ret = models.Book.objects.update(sale=F('sale')*2+5) print(ret)
引申:
如果要修改 char 字段咋办?
如: 把所有书名后面加上(第一版)
>>> from django.db.models.functions import Concat >>> from django.db.models import Value >>> models.Book.objects.all().update(title=Concat(F("title"), Value("("), Value("第一版"), Value(")")))
Q 查询
filter() 等方法中的关键字参数查询都是一起进行 "AND" 的. 如果你需要执行更复杂的查询(例如 OR 语句), 你可以使用 Q 对象.
# 导入 Q 查询 from django.db.models import Q # 语法 # Q(pk__gt=7) & Q(pk__lt=7) 与 # Q(pk__gt=7) | Q(pk__lt=7) 或 # ~Q(pk__gt=7) 非 ret = models.Book.objects.filter(Q(pk__gt=7)|Q(pk__lt=7)) ret = models.Book.objects.filter(~Q(pk__gt=6)|Q(pk__lt=7)) ret = models.Book.objects.filter(Q(~Q(pk__gt=7)|Q(pk__lt=7)) & ~Q(title__contains='合体') ) # 第二种方法 # 支持元组传入使用 更加灵活 ret = models.Book.objects.filter(Q(pk__lte=7)) ret = models.Book.objects.filter(Q(('pk__lte',7))) # 示例代码 field_name = 'pk' ret = models.Book.objects.filter(Q(('{}__lte'.format(field_name),7))) print(ret)
事务
事务和数据库当中的事务是一样的,
代表原子性 要成功都成功, 一条执行失败, 其余全失败
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings") import django django.setup() import datetime from app01 import models try: from django.db import transaction with transaction.atomic(): new_publisher = models.Publisher.objects.create(name="火星出版社") models.Book.objects.create(title="橘子物语", publish_date=datetime.date.today(), publisher_id=10) # 指定一个不存在的出版社 id except Exception as e: print(str(e)) #### 代码二 from django.db import transaction try: with transaction.atomic(): # 一系列操作 (原子性) models.Publisher.objects.create(name='小强出版社') models.Publisher.objects.create(name='小强出版社') models.Publisher.objects.create(name='小强出版社') models.Publisher.objects.create(name='小强出版社') models.Publisher.objects.create(name='小强出版社') except Exception as e: print(e) print('xxx')
Django 终端打印 sql 语句
settings.py 配置文件中添加以下内容:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
即为你的 Django 项目配置上一个名为 django.db.backends 的 logger 实例即可查看翻译后的 SQL 语句.
Django ORM 执行原生 sql 的方法
# extra # 在 QuerySet 的基础上继续执行子语句 # extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # select 和 select_params 是一组, where 和 params 是一组, tables 用来设置 from 哪个表 # Entry.objects.extra(select={ 'new_id': "select col from sometable where othercol> %s" }, select_params=(1,)) # Entry.objects.extra(where=['headline=%s'], params=['Lennon']) # Entry.objects.extra(where=["foo='a'OR bar ='a'","baz = 'a'"]) # Entry.objects.extra(select={ 'new_id': "select id from tb where id> %s" }, select_params=(1,), order_by=['-nid'])
举个例子:
models.UserInfo.objects.extra( select={'newid':'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 newid from app01_userinfo,app01_usertype where app01_userinfo.age> 18 order by app01_userinfo.age desc """ # 执行原生 SQL # 更高灵活度的方式执行原生 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()
Django Query_set 方法大全
################################################################## # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET # ################################################################## def all(self) # 获取所有的数据对象 def filter(self, *args, **kwargs) # 条件查询 # 条件可以是: 参数, 字典, Q def exclude(self, *args, **kwargs) # 条件查询 # 条件可以是: 参数, 字典, Q def select_related(self, *fields)
性能相关: 表之间进行 join 连表操作, 一次性获取关联的数据.
总结:
1. select_related 主要针一对一和多对一关系进行优化.
2. select_related 使用 SQL 的 JOIN 语句进行优化, 通过减少 SQL 查询的次数来进行优化, 提高性能.
def prefetch_related(self, *lookups)
性能相关: 多表连表操作时速度会慢, 使用其执行多次 SQL 查询在 Python 代码中实现连表操作.
总结:
1. 对于多对多字段 (ManyToManyField) 和一对多字段, 可以使用 prefetch_related()来进行优化.
2. prefetch_related()的优化方式是分别查询每个表, 然后用 Python 处理他们之间的关系.
def annotate(self, *args, **kwargs) # 用于实现聚合 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() # select distinct nid from userinfo
注: 只有在 PostgreSQL 中才能使用 distinct 进行去重
def order_by(self, *field_names) # 用于排序 models.UserInfo.objects.all().order_by('-id','age') def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # 构造额外的查询条件或者映射, 如: 子查询 Entry.objects.extra(select={'new_id': "select col from sometable where othercol> %s"}, select_params=(1,)) Entry.objects.extra(where=['headline=%s'], params=['Lennon']) Entry.objects.extra(where=["foo='a'OR bar ='a'","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): models.UserInfo.objects.defer('username','id')
或
models.UserInfo.objects.filter(...).defer('username','id') #映射中排除某列数据 def only(self, *fields): #仅取某个表中的数据 models.UserInfo.objects.only('username','id')
或
models.UserInfo.objects.filter(...).only('username','id') def using(self, alias):
指定使用的数据库, 参数为别名(setting 中的设置)
################################################## # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # ################################################## 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 对象 #################################### # METHODS THAT DO DATABASE QUERIES # #################################### 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): # 是否有结果
在 Python 脚本中调用 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)
来源: http://www.bubuko.com/infodetail-3304261.html