第02章 模型层

models

  • 模型介绍
  • 字段
  • 索引
  • 元类选项
  • 元类模型

模型介绍

一般来说,每个模型映射到一个数据库表ORM

  • 每个模型都是一个python类,这些类继承django.db.models.Model
  • 模型类的每个属性都相当于一个数据库字段
  • 综上,django给你一个自动生成访问数据库的API

快速上手

定义一个模型

  1. from django.db import models
  2. class Person(models.Model):
  3. first_name = models.CharField(max_length=30)
  4. last_name = models.CharField(max_length=30)

上面模型映射到的数据库表

  1. CREATE TABLE myapp_person (
  2. "id" serial NOT NULL PRIMARY KEY,
  3. "first_name" varchar(30) NOT NULL,
  4. "last_name" varchar(30) NOT NULL
  5. );

说明:

  • 表名中是自动从模型元数据中派生出来,但可以被改写。
    • 默认为$APP_NAME_$CLASS_NAME.lower()
  • 默认自动添加主键id字段,也可以自定义。

使用模型

定义了模型后,需要告诉Django你准备使用这些模型(映射模型)。修改项目设置文件中的INSTALLED_APP,在这个设置中添加包含你models.py文件的应用模块名

  1. INSTALLED_APPS = [
  2. #...
  3. 'myapp',
  4. #...
  5. ]

添加应用模块后,记得做数据库迁移:python manage.py migrate

基本操作

  1. def index(request):
  2. #1.使/用oRM添加一条数据到数据库中
  3. #book=Book(name='西游记",author='吴承恩',price=100)
  4. #book.save()
  5. #2.查询
  6. #2.1.根据主键进行查找
  7. #primary key
  8. #book=Book.objects.get(pk=2)
  9. #print(book)
  10. #2.2.根据其他条件进行查找
  11. #books=Book.objects.filter(name='三国演义).first()
  12. #print(books)
  13. #3.刷除数据#book=Book.objects.get(pk=1)
  14. #book.delete()
  15. #4.修改数据
  16. book=Book.objects.get(pk=2)
  17. book.price=200 book.save()
  18. return HttpResponse("图书添加成功!")

字段

模型中最重要的部分就是定义字段,模型(类)由类属性定义字段。

  1. from django.db import models
  2. class Musician(models.Model):
  3. first_name = models.CharField(max_length=50)
  4. last_name = models.CharField(max_length=50)
  5. instrument = models.CharField(max_length=100)

字段类型

每个字段由一个适当的Field类实例定义,Django会使用字段类型做一些事情

  • 用于定义数据库保存的列数据类型
  • 用于渲染HTML组件表单域
  • django后台用来做验证

常用字段

常用字段 说明
AutoField() 自增长字段,映射到DB中的int,一般不需要使用
BigAutoField() 同上,64位
BooleanField() 在模型层面是True/False,在DB中是 tinyint类型。如果没有指定,默认为None
NullBooleanField()
CharField(max_length=30) 使用varchar(254)保存;如果>254字符,就不建议使用了
TextField() DB中是longtext
InTegerField() 整形。-2147483648~2147483647
BigIntegerField() 大整形。-9223372036854775808~9223372036854775807
SmallIntegerField() 小整形。-32768~32767
PositiveIntegerField() 正整形,无符号。0~2147483647
PositiveSmallIntegerField() 正小整形 0~32767
EmailField() 类似CharField。varchar(254),用于支持ModelForm表单操作,在数据库中没有其他意义
FileField() 用来存储文件
ImageField() 用来存储图片,继承自FieField,自动验证上传的对象是否是有效图片。为了方便查询,带两个可选参数。
  • 依赖pillow库
  • 在DB中使用varchar(100)保存, |
    | | 浮点类型,DB中是float类型 |
    | DateField() | 日期类型,在python中是datetime.date,在DB中也是date,可以记录年月日。可以传递以下几个参数:
  1. auto_now:在每次这个数据保存时,使用当半的时间。比如作为一个记录修改日期的字段,可以将这个属性设置为True
  2. auto_now_add:在数据第一次添加进到的时候,使用当前的时间。比如作为一个记录第一次入库的字段,可以将这个属性设置为True |
    | DateTimeField() | 类似DateField(),还可以存储时间。DB中是 datetime类型,这个字段也支持上面字段的两个参数。 |
    | TimeField() | 时间类型。在DB上是time类型,在python中是datetime.time |
    | UUIDField() | 只能存储UUID字符串
    uuid是一个32位的全球唯一的字符串,一般用来做pk |
    | URLField() | |

官方地址: https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/#imagefield

关于时间

官方参考地址: https://docs.python.org/3/library/datetime.html

naive timeaware time

  • navie time : 幼稚时间(不带时区
  • aware time:成熟时间(带时区)
  1. # naive time 幼稚时间、天真时间 - 指不带时区的
  2. naive date: "2017-09-29"
  3. naive time: "15:00:00"
  4. naive datetime: "2017-09-29T15:00:00"
  5. # 指定时区(或者偏移量)来表示特定时间
  6. datetime in UTC "2017-09-29T15:00:00Z"
  7. datetime with timezone offset: "2017-09-29T15:00:00+0300"
  8. datetime with timezone: datetime="2017-09-29T15:00:00", timezone=
  9. - datetime with timezone: datetime="2017-09-29T15:00:00", timezone=

http://kean.github.io/post/naive-date-time

astimezone()

将一个时区的时间转换为另外一个时区的时间。这个方法只能被aware类型的时间调用。不能被naive类型的时间调用。

replace()

可以将一个时间的某些属性进行更改。

django.utils.timezone.now()

会根据settings.py中是否设置了USE_TZ=True获取当前的时间。如果设置了,那么就获取一个aware类型的UTC时间。如果没有设置,那么就会获取一个navie类型的时间。

django.utils.timezone.localtime方法:

会根据setting.py 中的TIME_ZONE来将一个aware类型的时间转换为TIME_ZONE

指定时区的

naiveaware介绍以及在 django 中的用法:

https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/

什么是UUID

UUID,是Universally Unique Identifier的缩写,UUID出现的目的,是为了让分布式系统可以不借助中心节点,就可以生成UUID来标识一些唯一的信息;

GUID,是Globally Unique Identifier的缩写,跟UUID是同一个东西,只是来源于微软。

规范定义

UUID来自于IETF发布的一个规范:A Universally Unique IDentifier (UUID) URN Namespace。

UUID来源于OSF的DCE规范,也就是RFC4122的前身

GUID来源于微软,注意RFC4122的作者之一是微软员工

UUID = 128bit_number

uuid = 123e4567-e89b-12d3-a456-426655440000

UUID版本

标准格式

UUID

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

  • M版本号,
  • N只能是8/9/a/b
    | | 说明 |
    | —- | —- |
    | UUID v1 | 基于时间+MAC (缺点暴露了MAC) |
    | UUID v2 | 基于时间+DEC |
    | UUID v3 | MD5(namespace, str) 为了向后兼容 |
    | UUID v4 | 基于随机数 |
    | UUID v3 | SHA1(namespace, str) |

字段示例代码

  1. # image字段参数
  2. ImageField(upload_to=None,height_field=None,Width_field=None,max_length=100, **options)

字段选项

每个字段可指定特定的选项,全部字段也有一组通用的选项。以下介绍常用选项

options 说明
null 布尔,默认False;指定数据库中的NULL(纯粹与DB相关)
  • 在使用基于字段串的字段时(CharField和TextField),官方不推荐使用此选项,也就是保持默认;默认值False。
  • 如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True。
  • 如果你的字段是BooleanField,那么对应的可空的字段则为NullBooleanField
    总结:不能为空,置“”空字符串;可以为空,置null |
    | blank | 布尔,默认False;验证表单是否允许为空(仅与验证相关) |
    | default | 默认值,可以为一个值/可调用对象,每次创建新对象时调用;但是不支持 lambda表达式。并且不支持列表/字典/集合。保存到DB之前,取一个默认值 |
    | help_text | 用于表单组件 form,即使没有在表单上使用,也可用于文档 |
    | db_column | 字段在数据库中的名字,如果没有设置,默认使用模型中属性的名字。 |
    | primary_key | 用于指定数据库主键。如果没有指定,django会自动添加一个IntegerField字段用作主键 |
    | unique | 用于指定唯一键,常用于手机号/邮箱 |
    | choices | 指定2元组的一个列表/元组 |

关于 null 选项:

布尔,默认False;指定数据库中的NULL(纯粹与DB相关)


在使用字段串相关的字段时(CharField和TextField),官方不推荐使用此选项,也就是保持默认;默认值False。因为django在处理字符串字段时,即使 null=False,如果你没有给这个Field传递任何值,那么django也会使用一个“”空字符串作为默认值存储进去。 因此如果再使用null=True,django会产生两种空值的情形(NULL或者空字符串)。

避免在基于字符串的字段(如CharField和TextField)上使用null。

如果基于字符串的字段为null = True,则表示它有两个可能的值为“无数据”:

  • NULL
  • 空字符串。

如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True。


如果你的Field是BooleanField,那么对应的可空的字段则为NullBooleanField

总结:

不能为空,置“”空字符串;可以为空,置null

选项示例代码

  1. # choices
  2. class Person(models.Model):
  3. SHIRT_SIZES = (
  4. ('S', 'Small'),
  5. ('M', 'Medium'),
  6. ('L', 'Large'),
  7. )
  8. name = models.CharField(max_length=60)
  9. shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
  10. # 第一个元素保存在数据库,第二个元素用于表单form组件显示。
  11. # 可以使用模型实例的 get_Model_display 方法获取显示值
  12. # > p = Person(name="Fred", shirt_size="L")
  13. # > p.save()
  14. # > p.shirt_size
  15. # 'L'
  16. # > p.get_shirt_size_display()
  17. # 'Large'

以上为常用字段选项,官网地址https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/

关于字段名字

除了ForeignKey/ManyToManyField/OneToOneField外,每个字段使用可选的第一个位置参数定义字段名称

  1. # 指定名称 "person's first name"
  2. first_name = models.CharField("person's first name", max_length=30)
  3. # 使用字段属性名做为默认名称 "first name"
  4. # 使用默认名称时,属性字段中的下划线会转换为空格
  5. first_name = models.CharField(max_length=30)

使用默认名称时,属性字段中的下划线会转换为空格

django自带用户模型

  1. from django.db import models
  2. # Create your models here.
  3. from django.contrib.auth.models import AbstractUser
  4. class UserProfile(AbstractUser):
  5. """
  6. 用户
  7. """
  8. name = models.CharField(max_length=30, null=True, blank=True, verbose_name="姓名")
  9. birthday = models.DateField(null=True, blank=True, verbose_name="出生年月")
  10. mobile = models.CharField(max_length=11,verbose_name="电话")
  11. gender = models.CharField(max_length=6, choices=(("male", "男"), ("female", "女")), default="female", verbose_name="性别")
  12. email = models.CharField(max_length=100, null=True, blank=True,verbose_name="邮箱" )
  13. class Meta:
  14. verbose_name = "用户"
  15. verbose_name_plural = verbose_name
  16. def __str__(self):
  17. return self.name
  18. class VerifyCode(models.Model):
  19. """
  20. 短信验证码
  21. """
  22. code = models.CharField(max_1ength=10, verbose_name="验证码")
  23. mobile = models.CharField(max_length=11, verbose_name="电话")
  24. add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
  25. class Meta:
  26. verbose_name = "短信验证码"
  27. verbose_name_plural = verbose_name
  28. def __str__(self):
  29. return self.code

自定义用户模型替换系统用户模型

  1. # setting.py
  2. # AUTH_USER_MODEL = 'myapp.MyUser'
  3. AUTH_USER_MODEL = 'users.UserProfile'
  4. # 上面的值表示Django应用的名称(必须位于INSTALLLED_APPS中)和你想使用的User模型的名称

Meta选项

功能:对于一些模型(表)级别的配置。

我们可以在模型中定义一个类,叫做Meta。然后在这个类中添加一些类属性来控制模型的作用。比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在Meta类中添加一个db_table的属性。

示例代码如下:

  1. class Book(mode1s.Mode1):
  2. name=models.CharField(max_lengthe=20null=False)
  3. desc=mode1s.CharField(max_fength=100, name='description'db_column='description1')
  4. class Meta
  5. db_table='book_model'
  6. ordering = ['pub_data']
  7. # ordering = ['create_time', 'id']
  8. # ordering = ['-create_time', 'id']

常用属性

选项 说明
abstract
app_label 如果这个APP是在INSTALLED_APPS
之外定义的,就必须声明
base_manager_name b
db_table 指定DB表名
模型映射到数据库中的表名。如果没有指定这个参数,那么在映射的时候将会使用模型名来作为默认的表名。 为了节省时间,DJANGO自动从模型类的名称和包含它的app中派生出表名。默认为:应用名_模型名 Bookbookstore_book
db_tablespace 定义DB表空间名称。
表空间:是DB的逻辑划分,一个表空间只能属于一个数据库。所有的数据库对象都存放在指定的表空间中。但主要存放的是表,所以称为表空间
get_latest_by 一个字段,或者字段列表,通常是DateField/DataTimeField/IntegerField
ordering 设置在提取数据的排序方式。后面章节会讲到如何查找数据。
indexes 指定模型上定义的索引列表
indexes=[models.Index(fields='last_name', 'first_name'])
verbose_name 对象的人类可读名称
verbose_name_plural 对象的复数名

参考地址: https://docs.djangoproject.com/zh-hans/2.0/ref/models/options/

外键和表关系

外键

在mySQL中,表有两种引擎,一种是ImnoDB,另外一种是myisam。如果使用的是ImnoDB引擎,是支持外键约束的。外键的存在使得ORM框架在处理表关系的时候异常的强大。因此这里我们首先来介绍下外键在Django中的使用。

类定义为class ForeignKey(to, on_delete,**options)

  • 第一个参数是引用的是哪个模型,
  • 第二个参数是在使用外键引用的模型数据被除了,这个字段该如何处理,比如有CASCADE、SET_NULL等。

数据库自动为ForeignKey创建索引。可以通过db_index=False禁用此功能。

数据库表示

django创建外键名称时,自动追加_id命名;如果要显示指定外键列名称必须使用db_column指定。

这里以一个实际案例来说明。比如有一个User和一个Article两个模型。一个User可以发表多篇文章,一个Article只能有一个Author,并且通过外键进行引用。那么相关的示例代码如下:

  1. class User(models.Model):
  2. username = models.CharField(max_length=20)
  3. password = models.CharField(max_length=100)
  4. class Article(models.Model):
  5. title = models.CharField(max_length=100)
  6. content = models.TextField()
  7. # on_delete 必须传递
  8. author = models.ForeignKey("User", on_delete=models.CSCADE)

以上使用Foreignkey来定义模型之间的关系。即在article的实例中可以通过author 属性来操作对应的user模型。这样使用起来非常的方便。示例代码如下:

  1. article=Article(title='abc', content='123')
  2. author = User(username='张三', password='111111')
  3. article.author = author
  4. article.save()

django 处理外键方式:自动在数据库中生成定义外键时的属性名_ID外键引用关系。

完整的外键示例

  1. role = models.ForeignKey(to="roles.Role", to_field="role_code", default=settings.ROLE_CODE, on_delete=models.DO_NOTHING, db_column='role_code',
  2. verbose_name="角色")
  • to : apps.models 或者 models
  • to_field : 模型上的字段
  • db_column: 指定保存时数据库字段名
  • on_delete : 指定外键删除时动作

外键的基本使用

  1. # models.py
  2. from django.db import models
  3. class Category(models.Model):
  4. name=models.CharField(max_length=100)
  5. class Article(models.Model):
  6. title=models.CharField(max_length=100)
  7. content=models.TextField()
  8. category=models.Foreignkey(to="Category",on_delete=models.CASCADE)

可选参数,下面介绍较为常用的几个:

  1. to_field: 设置关联到主表的字段,例如
  1. role = models.ForeignKey(to="roles.Role", to_field="role_code", default=1,
  2. on_delete=models.DO_NOTHING,
  3. verbose_name="角色")
  1. 注意:关联的字段的内容必须是不重复的。在默认情况下,Django关联到的字段是主表的主键(成为主键的要求之一就是不重复)
  1. related_name: 自定义一个名称,通常是复数形式,用于返回查询

当一张子表里,多个foreignkey指向同一个主表,related_name必须设置```python

ModelA

model_b = ForeignKeyField(to=ModelB, related_name=’model_as’)

访问ModelA与ModelB实例相关的实例
model_b_instance.model_as.all()

  1. 3. related_query_name: 用于django 查询集,它允许您过滤外键相关字段的反向关系。通常使用单数形式
  2. ```python
  3. # ModelA
  4. model_b = ForeignKeyField(to=ModelB, related_query_name='model_a')
  5. # 将使您可以model_a用作查询集中的查找参数
  6. ModelB.objects.filter(model_a=whatever)

官方文档:没有必要指定两者(或者其中之一),related_name和related_query_name。Django框架设置具有合理的默认值。

视图

  1. # 以下代码报错
  2. def index(request):
  3. article = Article(title='abc', content='111')
  4. category = Category(name="最新文章")
  5. article.category = category
  6. # 异常: save() prohibited to prevent data loss due to unsaved related objet 'category'
  7. article.save()
  8. # 修正
  9. def index(request):
  10. category = Category(name="最新文章")
  11. category.save()
  12. article = Article(title='abc', content='111')
  13. article.category = category
  14. article.save()

不同app之间的外键引用

  1. class Article(models.Model):
  2. title=models.CharField(max_length=100)
  3. content=models.TextField()
  4. # 不同app之间的外键引用,使用 appname.modelname
  5. category=models.Foreignkey("blog.Category",on_delete=models.CASCADE)

外键引用自己本身的模型

  1. class Comment(models.Model):
  2. content=models.TextField()
  3. origin_comment=models.Foreignkey("self",on_delete=models.CASCADE, null=True)
  4. #或者
  5. #origin_comment=models.Foreignkey("Comment",on_delete=models.CASCADE, null=True)

外键的删除操作

如果一个模型使用了外键。那么在对方那个模型被删除后,该进行什么样的操作。可以通过on_delete来指定。可以指定的类型有

选项 说明
models.CASCATE 级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。
models.PROTECT 受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。
models.SET_NULL 设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。
models.SET_DEFAULT 设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段一个默认值。
models.SET() 如果外键的那条数据被除了。那么将会获取SET函数中的值来作为这个外键的值.SET函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。
models.DO_NOTHING 不采取任何行为。一切全看数据库级别的约束。

举例

  1. class Article(models.Model):
  2. title=models.CharField(max_length=100)
  3. content=models.TextField()
  4. # 使用 SET_NULL 时,需要设置null
  5. category = models.ForeignKey("Category", on_delete=models.SET_NULL, null=True)
  6. # 使用 SET_DEFAULT,需要设置default
  7. category = models.ForeignKey("Category", on_delete=models.SET_DEFAULT, null=True, default=Category.objects.get(pk=4))
  8. # 使用SET()
  9. category = models.ForeignKey("Category", on_delete=models.SET(Category.objects.get(pk=4)), null=True)

注意: 以上这些选择只是django级别的,数据库级别依旧是RESTRICT

表关系

表之间的关系都是通过外键来进行关联的。而表之间的关系,无非就三种关系:

  • 一对一
  • 一对多(多对一)
  • 多对多

一对多

ForeignKey

  1. 应用场景:如文章和作者之间的关系,一个文章只能有一个作者,但是一个作者可以写多篇文章,这是典型的多对一关系。
  2. 实现方式:一对多或者多对一,都是通过ForeignKey来实现的。还是以文章和作者的案例进行讲解。

ForeignKey(othermodel, on_delete, **option)

  1. class User(models.Model):
  2. username = models.CharField(max_length=20)
  3. password = models.CharField(max_length=100)
  4. class Article(models.Model):
  5. title = models.CharField(max_length=100)
  6. content = models.TextField()
  7. author = models.ForeignKey("User",on_delete=models.CASCADE)

那么以后在给Article对象指定author,就可以使用以下代码来完成:

  1. article = Article(title='abc',content='123')
  2. author = User(username='xiongda',password='111111')
  3. # 要先保存到数据库中
  4. author.save()
  5. article.author = author
  6. article.save()

并且以后如果想要获取某个用户下所有的文章,可以通过article_set来实现。示例代码如下:

  1. user = User.objects.first()
  2. # 获取第一个用户写的所有文章
  3. articles = user.article_set.all()
  4. for article in articles:
  5. print(article)

一对一

OneToOneField(someModel)

可以理解为ForeignKey(SomeModel, unique=True)

两者的反向查询是有区别的:

  • ForeginKey 反向查询返回的是一个列表
  • OneToOneField 反向查询返回的是一个模型实例(因为一对一关系)

The Definitive Guide to Django http://djangobook.com/

OneToOneField 关系类似于 ForeignKey(unique=True),但是“reverse”时一边直接返回的是单个实例。

ForeignKey()的“reverse”返回的是一个 QuerySet

特点:

  1. 应用场景:比如一个用户表和一个用户信息表。在实际网站中,可能需要保存用户的许多信息,但是有些信息是不经常用的。如果把所有信息都存放到一张表中可能会影响查询效率,因此可以把用户的一些不常用的信息存放到另外一张表中我们叫做UserExtension。但是用户表User和用户信息表UserExtension就是典型的一对一了。
  2. 实现方式:Django为一对一提供了一个专门的Field叫做OneToOneField来实现一对一操作。示例代码如下:
  1. class User(models.Model):
  2. username = models.CharField(max_length=20)
  3. password = models.CharField(max_length=100)
  4. class UserExtension(models.Model):
  5. birthday = models.DateTimeField(null=True)
  6. school = models.CharField(blank=True,max_length=50)
  7. user = models.OneToOneField("User", on_delete=models.CASCADE)

UserExtension模型上增加了一个一对一的关系映射。其实数据库底层是在UserExtension这个表上增加了一个UNIQUE INDEX user_id外键 ,来和user表进行关联且保证一对一。

  1. CREATE TABLE `user_status` (
  2. `id` INT(11) NOT NULL AUTO_INCREMENT,
  3. `realname_status` INT(11) NULL DEFAULT NULL,
  4. `phone_status` INT(11) NULL DEFAULT NULL,
  5. `qq_status` INT(11) NULL DEFAULT NULL,
  6. `mail_status` INT(11) NULL DEFAULT NULL,
  7. `education_status` INT(11) NULL DEFAULT NULL,
  8. `user_id` INT(11) NOT NULL,
  9. PRIMARY KEY (`id`),
  10. # 参考
  11. UNIQUE INDEX `user_id` (`user_id`),
  12. CONSTRAINT `user_status_user_id_dd266ed5_fk_user_info_id` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`)
  13. )
  14. COLLATE='utf8_general_ci'
  15. ENGINE=InnoDB
  16. AUTO_INCREMENT=3
  17. ;

多对多

ManyToManyField

  1. 应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。
  2. 实现方式:Django为这种多对多的实现提供了专门的Field。叫做ManyToManyField。还是拿文章和标签为例进行讲解。示例代码如下:
  1. class Article(models.Model):
  2. title = models.CharField(max_length=100)
  3. content = models.TextField()
  4. tags = models.ManyToManyField("Tag",related_name="articles")
  5. class Tag(models.Model):
  6. name = models.CharField(max_length=50)

在数据库层面,实际上Django是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到articletag两张表的主键。

related_name和related_query_name

related_name

还是以UserArticle为例来进行说明。如果一个article想要访问对应的作者,那么可以通过author来进行访问。但是如果有一个user对象,想要通过这个user对象获取所有的文章,该如何做呢?这时候可以通过user.article_set来访问,这个名字的规律是模型名字小写_set。示例代码如下:

  1. user = User.objects.get(name='张三')
  2. user.article_set.all()

如果不想使用模型名字小写_set的方式,想要使用其他的名字,那么可以在定义模型的时候指定related_name。示例代码如下:

  1. class Article(models.Model):
  2. title = models.CharField(max_length=100)
  3. content = models.TextField()
  4. # 传递related_name参数,以后在方向引用的时候使用articles进行访问
  5. author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles')

以后在方向引用的时候。使用articles可以访问到这个作者的文章模型。示例代码如下:

  1. user = User.objects.get(name='张三')
  2. user.articles.all()

如果不想使用反向引用,那么可以指定related_name='+'。示例代码如下:

  1. class Article(models.Model):
  2. title = models.CharField(max_length=100)
  3. content = models.TextField()
  4. # 传递related_name参数,以后在方向引用的时候使用articles进行访问
  5. author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='+')

以后将不能通过user.article_set来访问文章模型了。

related_query_name

在查找数据的时候,可以使用filter进行过滤。使用filter过滤的时候,不仅仅可以指定本模型上的某个属性要满足什么条件,还可以指定相关联的模型满足什么属性。比如现在想要获取写过标题为abc的所有用户,那么可以这样写:

  1. users = User.objects.filter(article__title='abc')

如果你设置了related_namearticles,因为反转的过滤器的名字将使用related_name的名字,那么上例代码将改成如下:

  1. users = User.objects.filter(articles__title='abc')

可以通过related_query_name将查询的反转名字修改成其他的名字。比如article。示例代码如下:

  1. class Article(models.Model):
  2. title = models.CharField(max_length=100)
  3. content = models.TextField()
  4. # 传递related_name参数,以后在反向引用的时候使用articles进行访问
  5. author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles',related_query_name='article')

那么在做反向过滤查找的时候就可以使用以下代码:

  1. users = User.objects.filter(article__title='abc')

模型实例参考

官方文档:https://docs.djangoproject.com/zh-hans/2.0/ref/models/instances/

创建对象

  1. class Model(**kwargs)

keyword arguments 需要是定义的模型上的字段fields,注意:实例化不会访问数据库,调用**save()**时才会

验证对象

验证对象涉及三个步骤

  1. 验证模型字段 Model.clean_fields()
  2. 验证整个模型 Model.clean()
  3. 验证字段唯一性Model.validate_unique()

当调用 Model.full_clean()时,所有以上三个步骤都会执行

  1. Model.full_clean(exclude=None, validate_unique=True)[source]

此方法按此顺序调用Model.clean_fields() -> Model.clean() -> Model.validate_unique()

模型的操作

ORM框架中,所有模型相关的操作,比如添加/删除等。其实都是映射到数据库中一条数据的操作。因此模型操作也就是数据库表中数据的操作。

查询

查询是数据库操作中一个非常重要的技术。查询一般就是使用filter/exclude/get三个方法实现。我们可以在调用这些方法的时候传递不同的参数来实现查询需求。在ORM层面,这些查询条件都是使用field + __ +condition的方式来使用的。以下将那些常用的查询条件来一一解释。

filter 返回的是 QuerySet对象

get 返回的是模型对象

查询条件

查询条件 代码 说明
exact Article.objects.get(id__exact=14) 精确查询,底层SQL使用=
iexact Article.objects.get(title__iexact="hello word") 同上,忽略大小写;底层SQL使用的是like
contains Article.objects.filter(title__contains='hello') 包含判断(大小写敏感);底层是LIKE BINARY %hello%
icontains Article.objects.filter(title__icontains='hello') 同上,忽略大小写;底层LIKE %hello%
in Article.objects.filter(id__in=[1, 2, 3]) 指定的值是否在指定的容器中,容器可以为list/tuple或者任何一个可迭代对象,包括QuerySet对象
;底层是where id in (1, 2 3)
gt Article.objects.filter(id__gt=2) >
gte >=
lt <
lte <=
startswith 开始;底层LIKE BINARY hello%
istartswith 开始,忽略大小写;底层LIKE hello%
endswith 结尾;底层LIKE BINARY %hello
iendswith 结尾,忽略大小写;底层LIKE %hello
range Article.objects.filter(pub_date__range=(start_date, end_date)) 是否在给定的区间中
date Article.objects.filter(pub_date__date=date(2018, 3,29)) 针对date或者datetime
类型字段。可以指定date的范围来过滤,且支持链接调用
year Article.objects.filter(pub_date__year=2018
Article.objects.filter(pub_date__year_gte=2018 按年份查询
month 按月份查询
day 按日查询
week_day 按星期查询,2-6周一到周五,7周六,1周日(django1.11+)
time Article.objects.filter(pub_date__time=datetime.time(12,12,12)
Article.objects.filter(pub_date__time__range=(stime, etime) 按时间查询
isnull Article.objects.filter(pub_date__isnull=False 是否为空
regex Article.objects.filter(pub_date__regex=r"^hello" 按正则查询
iregex Article.objects.filter(pub_date__iregex=r"^hello" 同上,忽略大小写

说明:


QuerySet.query为ORM最终翻译的SQL语句

  • query只能用在QuerySet对象上

exactiecact就是等号与LIKE的区别,大部分情况下相同,


定义外键时,可以使用related_query_name来指定反向引用的名字


反向查询是将模型名字小写化。比如article__in。可以通过related_query_name来指定自己的方式,而不使用默认的方式。


反向引用是将模型名字小写化,然后再加上_set,比如article_set,可以通过related_name来指定自己的方式,而不使用默认的方式。


原时间与UTC时间的转换,可以使用from django.utils.timezone import make_aware函数


默认情况下MySQL的表中没有存储时区相关的信息。因此我们需要下载一些时区表的文件http://dev.mysql.com/downloads/timezones.html,然后添加到MySQL的配置路径中。如果你用的是:

  • windows:C:\ProgramData\MySQL\MySQL Server\5.7\Data\mysql中覆盖
  • linux:$ mysql_tzinfo_to_sql /usr/share/zoneinfo |mysql -D mysql -u root -p将时区文件更新到mysql

按时间**time**查询时可以使用**range**指定查询范围,官方文档指定的**between**无法使用

根据关联的表进行查询:

  1. categories = Category.objects.filter(article__title__contains("hello"))

聚合函数

分类汇总

如果你用原生SQL,则可以使用聚合函数 aggregate 来提供数据。比如提取某个商品销售的数量,那么可以使用Count,如果想要知道商品销售的平均价格,那么可以使用Avg

模块地址:from django.db.models import Avg

聚合函数 代码 说明
Avg Book.objects.aggregate(Avg('price'))
Book.objects.aggregate(my_avg=Avg('price')) 返回dict;可以使用关键字参数指定名字
Count Author.objects.aggregate(count=Count('email', distinct=True)) distinct
去重
Max Author.objects.aggregate(Max('age'),Main('age'))
Min Author.objects.aggregate(Max('age'),Main('age'))
Sum Book.objects.annotate(total=Sum("bookstore__price")).values("name", "total") 给Book表在查询时添加一个字段total,数据来源BookStore模型的price的总和。values方法是只提取name和total两个字段的值。

说明:

  • aggregate:返回dict类型;使用聚合函数后的字段和值,不会做分组(group by)
  • annotate:返回QuerySet;在原来模型字段的基础上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组(group by)

F 表达式

F表达式是用来优化ORM操作数据库的。

比如我们要将公司所有员工的薪水都增加1000元,如果按照正常的流程,应该是先从数据库中提取所有的员工工资到Python内存中,然后使用Python代码在员工工资的基础上增加1000元,最后再保存到数据库中。这里面涉及的流程就是,首先从数据库中提取数据到Python内存中,然后在Python内存中做完运算,之后再保存到数据库中。示例代码如下:

  1. employees = Employee.objects.all()
  2. for employee in employees:
  3. employee.salary += 1000
  4. employee.save()

而我们的F表达式可以优化这个流程,他可以不需要先把数据提取出来,计算完成后再保存回去,他可以直接执行SQL语句,就将员工的工资增加1000元。示例代码如下:

  1. form django.db.models import F
  2. Employee.object.update(salary=F("salary"+1000)

F表达式并不会马上从数据库中获取数据,而是在生成SQL语句时,动态获取传给F表达式的值。

比如如果想要获取作者表中,name和email相同的作者数据。如果使用F表达式,那么需要使用如下代码来完成:

  1. authors = Author.objects.all()
  2. for author in authors:
  3. if author.name == author.email:
  4. print(author)

如果使用F表达式,那么一行代码就可以

  1. from django.db.models import F
  2. author = Author.objects.filter(name=F("email"))

Q 对象

如果想要实现所有价格高于100元,并且评分达到9.0以上的图书。那么可以:

  1. books = Book.objects.filter(price__gte=100,rating__gte=9)

以上这个安全是一个并集查询,可以简单的通过传递多个条件进去来实现。

但是如果想要实现一些复杂的查询语句,比如要查询所有价格低于10元,或者是评分低于9分的书。那就没有办法通过传递多个条件进去实现了。这时候就需要使用Q表达式来实现

  1. from django.db.models import Q
  2. boos = Book.objects.filter(Q(price__lte=10) | Q(rating__lte=9))

以上是进行或运算,当然还可以进行其他的运算,比如有&/~等。

  1. from django.db.models import Q
  2. books = Book.objects.filter(Q(id=3))
  3. books = Book.objects.filter(Q(id=3) | Q(name__contains("记")))
  4. books = Book.objects.filter(Q(price__gte=100) & Q(name__contains("记")))
  5. books = Book.objects.filter(Q(name__contains("记") & ~Q(id=3))

QuerySet API

在使用QuerySet进行查询时,可以提供多种操作。比如过滤完后还要根据某个字段进行排序,那么这一系列的操作我们可以通过一个非常流畅的链式调用进行。比如要从文章表中获取标题为123,并且提取后要将结果根据发布的时间进行排序,那么可以使用以下方式来完成

  1. articles = Article.objects.filter(title='123').order_by('create_time')

返回QuerySet对象的方法

method 代码 说明
filter Article.objects.filter(title='123') 过滤
exclude 排除
annotate Article.objects.annotate(author_name=F("author__name")) QuerySet
中的每个对象添加一个使用查询表达式(聚合函数、F表达式、Q表达式、Func表达式等)的新字段。
order_by
values 指定要提取的数据字段,默认返回所有属性
values_list 同上,返回元组
all 获取ORM模型的QuerySet对象
select_related 在提取模型数据时,也提前将相联的数据提取出来
prefetch_related
defer
only
get
create
get_or_create obj, created = Person.objects.get_or_create( first_name='John', last_name='Lennon', defaults={'birthday': date(1940, 10, 9)}, ) 返回(object, created)
buld_create
count
exists
distinct
update Entry.objects.filter(id=10).update(comments_on=False) 如果你只是更新记录而不需要对模型对象做任何事件,最有效的方法是调用update(),而不是将模型对象加载到内存中。
delete

QuerySet转换为SQL的条件

生成一个QuerySet对象并不会马上转换为SQL语句去执行。

以下情况下QuerySet会被转换为SQL语句执行

  1. 迭代
  2. 使用步长做切片操作
  3. 调用len函数
  4. 调用list函数
  5. 判断:

model instances

  • 实例方法
  • 访问相关对象

Migrations

  • 迁移介绍
  • 操作参考
  • 构架编辑
  • 迁移

advanced

  • 管理
  • 祼SQL
  • 事务
  • Aggregation
  • 搜索
  • 自定义字段
  • 多数据库
  • 自定义查询
  • 查询表达式
  • 条件表达式
  • 数据库函数

other

  • 支持的数据库
  • 传统数据库
  • 提供初始数据
  • 数据库优化
  • PostgreSQL特定功能