model是django最复杂和重要的一层,负责与数据库打交道。
在使用时,我们在app/models.py中定义一个django.db.models.Model
的子类,这相当于数据库中的一张表,类里面的字段(属性)相当于数据库中的字段,该类的对象相当于一条数据。
简单例子
# app01/models.py
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=64)
假如我们以这个类创建表,那么其表名默认的为app01_question
(app名_小写类名)。而且django会自动创建一个自增主键id
。
使用Model
- 必须在INSTALL_APP中添加本app
- 使用命令
python manage.py makemigrations [app名字]
创建记录文件python manage.py migrate
同步到数据库
migrate命令将遍历INSTALLED_APPS设置中的所有项目,在数据库中创建对应的表,并打印出每一条动作信息。
migrate命令对所有还未实施的迁移记录进行操作,本质上就是将你对模型的修改体现到数据库中具体的表中。Django通过一张叫做django_migrations的表,记录并跟踪已经实施的migrate动作,通过对比获得哪些迁移尚未提交。
makemigrations命令创建记录文件,方便git使用。
字段
字段实质上就是类属性,由于django的查询语法有__
,所以对于字段的名称要一下要求:
- 不能为clean、save、delete等Django内置的模型API名字
- 不能包含
__
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
例子中的first_name、last_name、instrument都为字段,models.CharField
为一个django内置的字段类型,在数据库中就相当于数据类型。
内置字段
django Model中定义了一些常用的内置字段,下面列出常用的内置字段类型
类型 | 说明 |
---|---|
AutoField | 一个自动增加的整数类型字段。通常你不需要自己编写它,Django会自动帮你添加字段:id = models.AutoField(primary_key=True),这是一个自增字段,从1开始计数。如果你非要自己设置主键,那么请务必将字段设置为primary_key=True。Django在一个模型中只允许有一个自增字段,并且该字段必须为主键! |
BigAutoField | 64位整数类型自增字段,数字范围更大,从1到9223372036854775807 |
BigIntegerField | 64位整数字段(看清楚,非自增),类似IntegerField ,-9223372036854775808 到9223372036854775807。在Django的模板表单里体现为一个NumberInput标签。 |
BinaryField | 二进制数据类型。较少使用。 |
BooleanField | 布尔值类型。默认值是None。在HTML表单中体现为CheckboxInput标签。如果设置了参数null=True,则表现为NullBooleanSelect选择框。可以提供default参数值,设置默认值。 |
CharField | 最常用的类型,字符串类型。必须接收一个max_length参数,表示字符串长度不能超过该值。默认的表单标签是text input。 |
TextField | 用于储存大量的文本内容,在HTML中表现为Textarea标签,最常用的字段类型之一!如果你为它设置一个max_length参数,那么在前端页面中会受到输入字符数量限制,然而在模型和数据库层面却不受影响。只有CharField才能同时作用于两者。 |
DateField | class DateField(auto_now=False, auto_now_add=False, **options) , 日期类型。一个Python中的datetime.date的实例。在HTML中表现为DateInput标签。在admin后台中,Django会帮你自动添加一个JS日历表和一个“Today”快捷方式,以及附加的日期合法性验证。两个重要参数:(参数互斥,不能共存) auto_now:每当对象被保存时将字段设为当前日期,常用于保存最后修改时间。auto_now_add:每当对象被创建时,设为当前日期,常用于保存创建日期(注意,它是不可修改的)。设置上面两个参数就相当于给field添加了editable=False和blank=True属性。如果想具有修改属性,请用default参数。例子:pub_time = models.DateField(auto_now_add=True),自动添加发布时间。 |
DateTimeField | 日期时间类型。Python的datetime.datetime的实例。与DateField相比就是多了小时、分和秒的显示,其它功能、参数、用法、默认值等等都一样。 |
DecimalField | 固定精度的十进制小数。相当于Python的Decimal实例,必须提供两个指定的参数!参数max_digits:最大的位数,必须大于或等于小数点位数 。decimal_places:小数点位数,精度。 当localize=False时,它在HTML表现为NumberInput标签,否则是textInput类型。例子:储存最大不超过999,带有2位小数位精度的数,定义如下:models.DecimalField(..., max_digits=5, decimal_places=2)。 |
TimeField | 时间字段,Python中datetime.time的实例。接收同DateField一样的参数,只作用于小时、分和秒。 |
DurationField | 持续时间类型。存储一定期间的时间长度。类似Python中的timedelta。在不同的数据库实现中有不同的表示方法。常用于进行时间之间的加减运算。但是小心了,这里有坑,PostgreSQL等数据库之间有兼容性问题! |
EmailField | 邮箱类型,默认max_length最大长度254位。使用这个字段的好处是,可以使用Django内置的EmailValidator进行邮箱格式合法性验证。 |
FileField | class FileField(upload_to=None, max_length=100, **options) 上传文件类型,后面单独介绍。 |
FilePathField | 文件路径类型,后面单独介绍 |
FloatField | 浮点数类型,对应Python的float。参考整数类型字段。 |
ImageField | 图像类型,后面单独介绍。 |
IntegerField | 整数类型,最常用的字段之一。取值范围-2147483648到2147483647。在HTML中表现为NumberInput或者TextInput标签。 |
GenericIPAddressField | class GenericIPAddressField(protocol='both', unpack_ipv4=False, **options) ,IPV4或者IPV6地址,字符串形式,例如192.0.2.30或者2a02:42fe::4。在HTML中表现为TextInput标签。参数protocol默认值为‘both’,可选‘IPv4’或者‘IPv6’,表示你的IP地址类型。 |
JSONField | JSON类型字段。Django3.1新增。签名为class JSONField(encoder=None,decoder=None,**options) 。其中的encoder和decoder为可选的编码器和解码器,用于自定义编码和解码方式。如果为该字段提供default值,请务必保证该值是个不可变的对象,比如字符串对象。 |
PositiveBigIntegerField | 正的大整数,0到9223372036854775807 |
PositiveIntegerField | 正整数,从0到2147483647 |
PositiveSmallIntegerField | 较小的正整数,从0到32767 |
SlugField | slug是一个新闻行业的术语。一个slug就是一个某种东西的简短标签,包含字母、数字、下划线或者连接线,通常用于URLs中。可以设置max_length参数,默认为50。 |
SmallAutoField | Django3.0新增。类似AutoField,但是只允许1到32767。 |
SmallIntegerField | 小整数,包含-32768到32767。 |
URLField | 一个用于保存URL地址的字符串类型,默认最大长度200。 |
UUIDField | 用于保存通用唯一识别码(Universally Unique Identifier)的字段。使用Python的UUID类。在PostgreSQL数据库中保存为uuid类型,其它数据库中为char(32)。这个字段是自增主键的最佳替代品。 |
使用UUIDField
UUIDField
数据库无法自己生成uuid,因此需要如下使用default参数:
import uuid
from django.db import models
class MyUUIDModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# 注意不要写成default=uuid.uuid4()
# 其它字段
自定义字段
字段参数
所有的模型字段都可以接收一定数量的参数,比如CharField
至少需要一个max_length
参数。下面的这些参数是所有字段都可以使用的,并且是可选的。
-
null
该值为True时,Django在数据库用NULL保存空值。默认值为False。对于保存字符串类型数据的字段,请尽量避免将此参数设为True,那样会导致两种‘没有数据’的情况,一种是NULL,另一种是空字符串''。Django 的惯例是使用空字符串而不是 NULL。 -
blank
True时,字段可以为空。默认False。和null参数不同的是,null是纯数据库层面的,而blank是验证相关的,它与表单验证是否允许输入框内为空有关,与数据库无关。所以要小心一个null为False,blank为True的字段接收到一个空值可能会出bug或异常。 -
verbose_name
为字段设置一个人类可读,更加直观的别名。
对于每一个字段类型,除了ForeignKey、ManyToManyField和OneToOneField这三个特殊的关系类型,其第一可选位置参数都是verbose_name。如果没指定这个参数,Django会利用字段的属性名自动创建它,并将下划线转换为空格。下面这个例子的verbose name是"person’s first name":
first_name = models.CharField("person's first name", max_length=30)
下面这个例子的verbose name是"first name":first_name = models.CharField(max_length=30)
对于外键、多对多和一对一字字段,由于第一个参数需要用来指定关联的模型,因此必须用关键字参数verbose_name来明确指定。如下:poll = models.ForeignKey(
Poll,
on_delete=models.CASCADE,
verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
verbose_name="related place",
)
另外,你无须大写verbose_name的首字母,Django自动为你完成这一工作。 -
default
字段的默认值,可以是值或者一个可调用对象。如果是可调用对象,那么每次创建新对象时都会调用。设置的默认值不能是一个可变对象,比如列表、集合等等。lambda匿名函数也不可用于default的调用对象,因为匿名函数不能被migrations序列化。
注意:在某种原因不明的情况下将default设置为None,可能会引发intergyerror:not null constraint failed,即非空约束失败异常,导致python manage.py migrate失败,此时可将None改为False或其它的值,只要不是None就行。 -
primary_key
如果你没有给模型的任何字段设置这个参数为True,Django将自动创建一个AutoField自增字段,名为‘id’,并设置为主键。也就是id = models.AutoField(primary_key=True)。
如果你为某个字段设置了primary_key=True,则当前字段变为主键,并关闭Django自动生成id主键的功能。
primary_key=True隐含null=False和unique=True的意思。一个模型中只能有一个主键字段!
另外,主键字段不可修改,如果你给某个对象的主键赋个新值实际上是创建一个新对象,并不会修改原来的对象。
注:主键可以使用"pk"代指,如:Question.object.get(pk=1)
from django.db import models
class Fruit(models.Model):
name = models.CharField(max_length=100, primary_key=True)
###############
>>> fruit = Fruit.objects.create(name='Apple')
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
['Apple', 'Pear']
-
unique
为True时,在整个数据表内该字段的数据不可重复。
注意:对于ManyToManyField和OneToOneField关系类型,该参数无效。
注意: 当unique=True时,db_index参数无须设置,因为unqiue隐含了索引。 -
editable
如果设为False,那么当前字段将不会在admin后台或者其它的ModelForm表单中显示,同时还会被模型验证功能跳过。参数默认值为True。 -
error_messages
用于自定义错误信息。参数接收字典类型的值。
字典的键可以是null、 blank、 invalid、 invalid_choice、 unique和unique_for_date其中的一个。 -
help_text
额外显示在表单部件上的帮助文本。即便你的字段未用于表单,它对于生成文档也是很有用的。
该帮助文本默认情况下是可以带HTML代码的,具有风险:
help_text="Please use the following format: <em>YYYY-MM-DD</em>."
所以使用时请注意转义为纯文本,防止脚本攻击。 -
choices
用于页面上的选择框标签,需要先提供一个二维的二元元组,第一个元素表示存在数据库内真实的值,第二个表示页面上显示的具体内容。在浏览器页面上将显示第二个元素的值。例如:YEAR_IN_SCHOOL_CHOICES = ( ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'), )
一般来说,最好将选项定义在类里,并取一个直观的名字,如下所示:
from django.db import models class Student(models.Model): FRESHMAN = 'FR' SOPHOMORE = 'SO' JUNIOR = 'JR' SENIOR = 'SR' YEAR_IN_SCHOOL_CHOICES = ( (FRESHMAN, 'Freshman'), (SOPHOMORE, 'Sophomore'), (JUNIOR, 'Junior'), (SENIOR, 'Senior'), ) year_in_school = models.CharField( max_length=2, choices=YEAR_IN_SCHOOL_CHOICES, default=FRESHMAN, ) def is_upperclass(self): return self.year_in_school in (self.JUNIOR, self.SENIOR)
注意:每当 choices 的顺序变动时将会创建新的迁移。
如果一个模型中有多个字段需要设置choices,可以将这些二维元组组合起来,显得更加整洁优雅,例如下面的做法:
MEDIA_CHOICES = [ ('Audio', ( ('vinyl', 'Vinyl'), ('cd', 'CD'), ) ), ('Video', ( ('vhs', 'VHS Tape'), ('dvd', 'DVD'), ) ), ('unknown', 'Unknown'), ]
反过来,要获取一个choices的第二元素的值,可以使用
get_FOO_display()
方法,其中的FOO用字段名代替。对于下面的例子:from django.db import models class Person(models.Model): SHIRT_SIZES = ( ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ) name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
使用方法:
>>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() >>> p.shirt_size 'L' >>> p.get_shirt_size_display() 'Large'
从Django3.0开始,新增了
TextChoices
、IntegerChoices
和Choices
三个类,用来达到类似Python的enum枚举库的作用,
如果文本或数字类型不满足你的要求,你也可以继承Choice类,自己写。比如下面就创建了一个时间类型选项的choices类:class MoonLandings(datetime.date, models.Choices): APOLLO_11 = 1969, 7, 20, 'Apollo 11 (Eagle)' APOLLO_12 = 1969, 11, 19, 'Apollo 12 (Intrepid)' APOLLO_14 = 1971, 2, 5, 'Apollo 14 (Antares)' APOLLO_15 = 1971, 7, 30, 'Apollo 15 (Falcon)' APOLLO_16 = 1972, 4, 21, 'Apollo 16 (Orion)' APOLLO_17 = 1972, 12, 11, 'Apollo 17 (Challenger)'
最后,如果想设置空标签,可以参考下面的做法:
class Answer(models.IntegerChoices): NO = 0, _('No') YES = 1, _('Yes') __empty__ = _('(Unknown)')
-
db_column
该参数用于定义当前字段在数据表内的列名。如果未指定,Django将使用字段名作为列名。 -
db_index
该参数接收布尔值。如果为True,数据库将为该字段创建索引。 -
db_tablespace
用于字段索引的数据库表空间的名字,前提是当前字段设置了索引。默认值为工程的DEFAULT_INDEX_TABLESPACE设置。如果使用的数据库不支持表空间,该参数会被忽略。 -
unique_for_date
日期唯一。
可能不太好理解。举个栗子,如果你有一个名叫title的字段,并设置了参数unique_for_date="pub_date",那么Django将不允许有两个模型对象具备同样的title和pub_date。有点类似联合约束。 -
unique_for_month
同上,只是月份唯一。 -
unique_for_year
同上,只是年份唯一。 -
validators
运行在该字段上的验证器的列表。
Meta类
模型的元数据是对字段的补充,是所有不是字段的东西,比如排序选项( ordering ),数据库表名( db_table ),或是阅读友好的单复数名( verbose_name 和 verbose_name_plural )。这些都不是必须的,并且在模型当中添加 Meta类 也完全是可选的。
-
verbose_name
最常用的元数据之一!用于设置模型对象的直观、人类可读的名称,用于在各种打印、页面展示等场景。可以用中文。例如:
verbose_name = "story"
verbose_name = "披萨"
如果你不指定它,那么Django会使用小写的模型名作为默认值。 -
verbose_name_plural
英语有单数和复数形式。这个就是模型对象的复数名,比如“apples”。因为我们中文通常不区分单复数,所以保持和verbose_name一致也可以。
verbose_name_plural = "stories"
verbose_name_plural = "披萨"
verbose_name_plural = verbose_name
如果不指定该选项,那么默认的复数名字是verbose_name加上‘s’ -
ordering
最常用的元数据之一了!用于指定该模型生成的所有对象的排序方式,接收一个字段名组成的元组或列表。默认按升序排列,如果在字段名前加上字符“-”则表示按降序排列,如果使用字符问号“?”表示随机排列。请看下面的例子:
这个顺序是你通过查询语句,获得Queryset后的列表内元素的顺序,切不可和前面的get_latest_by等混淆。
ordering = ['pub_date'] # 表示按'pub_date'字段进行升序排列 ordering = ['-pub_date'] # 表示按'pub_date'字段进行降序排列 ordering = ['-pub_date', 'author'] # 表示先按'pub_date'字段进行降序排列,再按`author`字段进行升序排列。
-
unique_together
这个元数据是非常重要的一个!它等同于数据库的联合约束!举个例子,假设有一张用户表,保存有用户的姓名、出生日期、性别和籍贯等等信息。要求是所有的用户唯一不重复,可现在有好几个叫“张伟”的,如何区别它们呢?(不要和我说主键唯一,这里讨论的不是这个问题)
我们可以设置不能有两个用户在同一个地方同一时刻出生并且都叫“张伟”,使用这种联合约束,保证数据库能不能重复添加用户(也不要和我谈小概率问题)。在Django的模型中,如何实现这种约束呢?
使用unique_together,也就是联合唯一!
比如:
unique_together = [['name', 'birth_day', 'address'],......]
这样,哪怕有两个在同一天出生的张伟,但他们的籍贯不同,也就是两个不同的用户。一旦三者都相同,则会被Django拒绝创建。这个元数据选项经常被用在admin后台,并且强制应用于数据库层面。unique_together接收一个二维的列表,每个元素都是一维列表,表示一组联合唯一约束,可以同时设置多组约束。为了方便,对于只有一组约束的情况下,可以简单地使用一维元素,例如:
unique_together = ['name', 'birth_day', 'address']
联合唯一无法作用于普通的多对多字段。 -
index_together
联合索引,用法和特性类似unique_together。 -
abstract
如果abstract=True,那么模型会被认为是一个抽象模型。抽象模型本身不实际生成数据库表,而是作为其它模型的父类,被继承使用。具体内容可以参考Django模型的继承。 -
app_label
如果定义了模型的app没有在INSTALLED_APPS中注册,则必须通过此元选项声明它属于哪个app,例如:
app_label = 'myapp' -
base_manager_name
模型的_base_manager管理器的名字,默认是'objects'。模型管理器是Django为模型提供的API所在。 -
db_table
指定在数据库中,当前模型生成的数据表的表名。比如:
db_table = 'my_freinds'
如果你没有指定这个选项,那么Django会自动使用app名和模型名,通过下划线连接生成数据表名,比如app_book。
不要使用SQL语言或者Python的保留字,注意冲突。
友情建议:使用MySQL和MariaDB数据库时,db_table用小写英文。 -
db_tablespace
自定义数据库表空间的名字。默认值是项目的DEFAULT_TABLESPACE配置项指定的值。 -
default_manager_name
模型的_default_manager管理器的名字。 -
default_related_name
默认情况下,从一个模型反向关联设置有关系字段的源模型,我们使用<model_name>_set
,也就是源模型的名字+下划线+set。这个元数据选项可以让你自定义反向关系名,同时也影响反向查询关系名!看下面的例子:
from django.db import models class Foo(models.Model): pass class Bar(models.Model): foo = models.ForeignKey(Foo, on_delete=models.CASCADE) class Meta: default_related_name = 'bars' # 关键在这里
具体的使用差别如下:
>>> bar = Bar.objects.get(pk=1) >>> # 不能再使用"bar"作为反向查询的关键字了。 >>> Foo.objects.get(bar=bar) >>> # 而要使用你自己定义的"bars"了。 >>> Foo.objects.get(bars=bar)
-
get_latest_by
Django管理器给我们提供有latest()和earliest()方法,分别表示获取最近一个和最前一个数据对象。但是,如何来判断最近一个和最前面一个呢?也就是根据什么来排序呢?
get_latest_by元数据选项帮你解决这个问题,它可以指定一个类似 DateField、DateTimeField或者IntegerField这种可以排序的字段,作为latest()和earliest()方法的排序依据,从而得出最近一个或最前面一个对象。例如:get_latest_by = "order_date" # 根据order_date升序排列 get_latest_by = ['-priority', 'order_date'] # 根据priority降序排列,如果发生同序,则接着使用order_date升序排列
-
managed
该元数据默认值为True,表示Django将按照既定的规则,管理数据库表的生命周期。如果设置为False,将不会针对当前模型创建和删除数据库表,也就是说Django暂时不管这个模型了。
在某些场景下,这可能有用,但更多时候,你可以忘记该选项。
-
order_with_respect_to
这个选项不好理解。其用途是根据指定的字段进行排序,通常用于关系字段。看下面的例子:from django.db import models class Question(models.Model): text = models.TextField() # ... class Answer(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) # ... class Meta: order_with_respect_to = 'question'
上面在Answer模型中设置了order_with_respect_to = 'question',这样的话,Django会自动提供两个API,get_RELATED_order()和set_RELATED_order(),其中的RELATED用小写的模型名代替。假设现在有一个Question对象,它关联着多个Answer对象,下面的操作返回包含关联的Anser对象的主键的列表
[1,2,3]
:>>> question = Question.objects.get(id=1) >>> question.get_answer_order() [1, 2, 3]
我们可以通过set_RELATED_order()方法,指定上面这个列表的顺序:
>>> question.set_answer_order([3, 1, 2])
同样的,关联的对象也获得了两个方法
get_next_in_order()
和get_previous_in_order()
,用于通过特定的顺序访问对象,如下所示:>>> answer = Answer.objects.get(id=2) >>> answer.get_next_in_order() <Answer: 3> >>> answer.get_previous_in_order() <Answer: 1>
-
permissions
该元数据用于当创建对象时增加额外的权限。它接收一个所有元素都是二元元组的列表或元组,每个元素都是(权限代码, 直观的权限名称)的格式。比如下面的例子:这个Meta选项非常重要,和
auth
框架的权限系统紧密相关。permissions = (("can_deliver_pizzas", "可以送披萨"),)
-
default_permissions
Django默认会在建立数据表的时候就自动给所有的模型设置('add', 'change', 'delete')的权限,也就是增删改。你可以自定义这个选项,比如设置为一个空列表,表示你不需要默认的权限,但是这一操作必须在执行migrate命令之前。也是配合auth
框架使用。 -
proxy
如果设置了proxy = True,表示使用代理模式的模型继承方式。具体内容与abstract选项一样,参考模型继承。 -
required_db_features
声明模型依赖的数据库功能。比如['gis_enabled']
,表示模型的建立依赖GIS功能。 -
required_db_vendor
声明模型支持的数据库。Django默认支持sqlite, postgresql, mysql, oracle。 -
select_on_save
决定是否使用1.6版本之前的django.db.models.Model.save()算法保存对象。默认值为False。这个选项我们通常不用关心。 -
indexes
接收一个应用在当前模型上的索引列表,如下例所示:from django.db import models class Customer(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) class Meta: indexes = [ models.Index(fields=['last_name', 'first_name']), models.Index(fields=['first_name'], name='first_name_idx'), ]
-
constraints
为模型添加约束条件。通常是列表的形式,每个列表元素就是一个约束。from django.db import models class Customer(models.Model): age = models.IntegerField() class Meta: constraints = [ models.CheckConstraint(check=models.Q(age__gte=18), name='age_gte_18'), ]
上例中,会检查age年龄的大小,不得低于18。
-
label
前面介绍的元数据都是可修改和设置的,但还有两个只读的元数据,label就是其中之一。
label等同于app_label.object_name。例如polls.Question,polls是应用名,Question是模型名。 -
label_lower
同上,不过是小写的模型名。
操作
增
假如models.py文件这样定义:
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=32)
author = models.ForeignKey("Author", on_delete=models.CASCADE)
class Author(models.Model):
name = models.CharField(max_length=16)
两种方法:
- 对象.save
def test_model(request):
author = Author(name="lczmx")
blog = Blog(title="母猪产后护理", author=author)
author.save()
blog.save()
return HttpResponse("success!!!!!!!")
- 类.create()
def test_model(request):
author = Author.objects.create(name="李华")
Blog.objects.create(title="英语作文速成", author=author)
return HttpResponse("success!!!!!!!")
删
具体方法是找到要删除的记录(即类对象或QuerySet对象),然后使用delete方法:
def test_model(request):
Author.objects.get(pk=2).delete()
return HttpResponse("success!!!!!!!")
改
同样有两种方法:
- 第一种:
根据修改对象的属性,然后调用.save()
def test_model(request):
author = Author.objects.get(pk=1)
author.name = "李华"
author.save()
return HttpResponse("success!!!!!!!")
- 第二种:
使用QuerySet.update()方法。
def test_model(request):
Author.objects.filter(pk=1).update(name="lczmx")
return HttpResponse("success!!!!!!!")
查
想要从数据库内检索对象,你需要基于模型类,通过管理器(Manager)操作数据库并返回一个查询结果集(QuerySet)。
每个QuerySet代表一些数据库对象的集合。它可以包含零个、一个或多个过滤器(filters)。Filters缩小查询结果的范围。在SQL语法中,一个QuerySet相当于一个SELECT语句,而filter则相当于WHERE或者LIMIT一类的子句。
QuerySet的api
这里是objects管理器返回的。
大多数QuerySets(不是所有)的方法都会返回一个新的QuerySet,这是为了实现链式调用而设计的。
以下的方法都将返回一个新的QuerySets。重点是加粗的几个API,其它的使用场景很少。
方法名 | 解释 |
---|---|
filter() | 过滤查询对象。 |
get() | 获取一个对象 |
exclude() | 排除满足条件的对象 |
aggregate() | 聚合查询 |
annotate() | 为查询集添加注解或者聚合内容 |
order_by() | 对查询集进行排序 |
reverse() | 反向排序 |
distinct() | 对查询集去重 |
values() | 返回包含对象具体值的字典的QuerySet |
values_list() | 与values()类似,只是返回的是元组而不是字典。 |
dates() | 根据日期获取查询集 |
datetimes() | 根据时间获取查询集 |
none() | 创建空的查询集 |
all() | 获取所有的对象 |
union() | 并集 |
intersection() | 交集 |
difference() | 差集 |
select_related() | 附带查询关联对象,利用缓存提高效率 |
prefetch_related() | 预先查询,提高效率 |
extra() | 将被废弃的方法 |
defer() | 不加载指定字段,也就是排除一些列的数据 |
only() | 只加载指定的字段,仅选择需要的字段 |
using() | 选择数据库 |
select_for_update() | 锁住选择的对象,直到事务结束。 |
raw() | 接收一个原始的SQL查询 |
-
filter()
filter(**kwargs)
返回满足查询参数的对象集合。
查找的参数(**kwargs
)应该满足下文字段查找中的格式。多个参数之间是和AND的关系。 -
get()
get(**kwargs)
注意:使用get()方法和使用filter()方法然后通过[0]
的方式分片,有着不同的地方。看似两者都是获取单一对象。但是,如果在查询时没有匹配到对象,那么get()方法将抛出DoesNotExist异常。这个异常是模型类的一个属性,在上面的例子中,如果不存在主键为1的Entry对象,那么Django将抛出Entry.DoesNotExist异常。
类似地,在使用get()方法查询时,如果结果超过1个,则会抛出MultipleObjectsReturned异常,这个异常也是模型类的一个属性。 -
exclude()
exclude(**kwargs)
返回一个新的QuerySet,它包含不满足给定的查找参数的对象。与filter相反。 -
aggregate()
聚合查询
聚合函数from django.db.models import Avg, Min, Max, Sum, Count
使用:Blog.objects.aggregate(new_field=Count('entry'))
若不设置字段,默认为
字段名_聚合函数名
。 -
annotate()
annotate(args, *kwargs)
注解查询集。
可以使用聚合函数。
注解用于为查询集中的每个对象添加额外的统计信息属性。例如,如果正在操作一个Blog列表,你可能想知道每个Blog有多少Entry:
>>> from django.db.models import Count >>> q = Blog.objects.annotate(new_field=Count('entry'))
这样他就多了一个new_field字段了。
有values的话,values要在annotate之前。 -
order_by()
默认情况下,根据模型的Meta类中的ordering属性对QuerySet中的对象进行排序。
可以通过查询时的order_by()改变上述默认行为,指定新的排序依据:Blog.objects.all().order_by('title')
默认升序,把字段前加上
-
为降序:Blog.objects.all().order_by('-title')
跨表的话,使用
__
即可。 -
reverse()
反向排序QuerySet中返回的元素。 第二次调用reverse()将恢复到原有的排序。
要获取QuerySet中最后五个元素,可以这样做:my_queryset.reverse()[:5]
这与Python直接使用负索引有点不一样。 Django不支持负索引,只能曲线救国。
-
distinct()
distinct(*fields)
去除查询结果中重复的行。 -
values()
values(fields, *expressions)
返回一个包含数据字典的queryset,而不是模型实例。
每个字典表示一个对象,键对应于模型对象的属性名称。
你可以简单地理解为Python字典的values。
在values()子句中的聚合应用于相同values()子句中的其他参数之前。 如果需要按另一个值分组,请将其添加到较早的values()子句中。>>> from django.db.models import Count >>> Blog.objects.values('author', entries=Count('entry')) <QuerySet [{'author': 1, 'entries': 20}, {'author': 1, 'entries': 13}]> >>> Blog.objects.values('author').annotate(entries=Count('entry')) <QuerySet [{'author': 1, 'entries': 33}]>
-
values_list()
values_list(*fields, flat=False, named=False)
与values()类似,只是在迭代时返回的是元组而不是字典。
每个元组包含传递给values_list()调用的相应字段或表达式的值,因此第一个项目是第一个字段等。
参数与values类似。 -
all()
返回当前QuerySet(或QuerySet子类)的副本。通常用于获取全部QuerySet对象。 -
raw()
raw(raw_query, params=None, translations=None)
接收一个原始的SQL查询,执行它并返回一个django.db.models.query.RawQuerySet实例。
字段查询参数
get、filter、exclude等api的使用过程中需要用到字段,进而对数据进行筛选。
Django的数据库API支持20多种查询类型,下表列出了所有的字段查询参数:
字段 | 说明 |
---|---|
exact | 精确匹配,默认 |
iexact | 不区分大小写的精确匹配 |
contains | 包含匹配 |
icontains | 不区分大小写的包含匹配 |
in | 在..之内的匹配 |
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
startswith | 从开头匹配 |
istartswith | 不区分大小写从开头匹配 |
endswith | 从结尾处匹配 |
iendswith | 不区分大小写从结尾处匹配 |
range | 范围匹配 |
date | 日期匹配 |
year | 年份 |
iso_year | 以ISO 8601标准确定的年份 |
month | 月份 |
day | 日期 |
week | 第几周 |
week_day | 周几 |
iso_week_day | 以ISO 8601标准确定的星期几 |
quarte | r 季度 |
time | 时间 |
hour | 小时 |
minute | 分钟 |
second | 秒 |
regex | 区分大小写的正则匹配 |
iregex | 不区分大小写的正则匹配 |
使用方法:
字段__查询参数
如:
Blog.objects.filter(name__contains="python")
查询参数可以连用:
Event.objects.filter(timestamp__second__gte=31)
懒加载
QuerySets都是懒惰的
一个创建QuerySets的动作不会立刻导致任何的数据库行为。你可以不断地进行filter动作一整天,Django不会运行任何实际的数据库查询动作,直到QuerySets被提交(evaluated)。
简而言之就是,只有碰到某些特定的操作,Django才会将所有的操作体现到数据库内,否则它们只是保存在内存和Django的层面中。这是一种提高数据库查询效率,减少操作次数的优化设计。看下面的例子:
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
上面的例子,看起来执行了3次数据库访问,实际上只是在print语句时才执行1次访问。通常情况,QuerySets的检索不会立刻执行实际的数据库查询操作,直到出现类似print的请求,也就是所谓的evaluated。
那么如何判断哪种操作会触发真正的数据库操作呢?简单的逻辑思维如下:
第一次需要真正操作数据的值的时候。比如上面print(q),如果你不去数据库拿q,print什么呢?
落实修改动作的时候。你不操作数据库,怎么落实?
跨表查询
django中以__
表示跨表,想要跨表的话,两个表之间必须要建立表关系(如何建立表关系,见下文)。
假如有这样两张表:
class Blog(models.Model):
title = models.CharField(max_length=32)
author = models.ForeignKey("Author", on_delete=models.CASCADE)
class Author(models.Model):
name = models.CharField(max_length=16)
那么,我们就可以通过正向查询和反向查询查找信息:
- 正向查询,即字表查主表,是有字段的那边查没有字段的那边。
Blog.objects.filter(author__id=1)
- 反向查询,需要借助django的一个管理器,默认为
类名小写_set
,可以在Meta中定义。
注意:对象是类对象,非管理器或QuerySet,因为管理器是属于模型类的!
Author.objects.get(pk=1).blog_set.all()
F查询
到目前为止的例子中,我们都是将模型字段与常量进行比较。但是,如果你想将模型的一个字段与同一个模型的另外一个字段进行比较该怎么办?
使用Django提供的F表达式!
例如,为了查找comments数目多于pingbacks数目的Entry,可以构造一个F()对象来引用pingback数目,并在查询中使用该F()对象:
>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))
Django支持对F()对象进行加、减、乘、除、求余以及幂运算等算术操作。两个操作数可以是常数和其它F()对象。
例如查找comments数目比pingbacks两倍还要多的Entry,我们可以这么写:
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)
Q查询
普通filter查询里的条件都是“and”逻辑,如果你想实现“or”逻辑怎么办?用Q查询!
Q来自django.db.models.Q
,用于封装关键字参数的集合,可以作为关键字参数用于filter、exclude和get等函数。 可以使用&
或者|
或~
来组合Q对象,分别表示与、或、非逻辑。它将返回一个新的Q对象。例如:
Blog.objects.filter(Q(pk=1) | Q(pk=3))
补充
- QuerySet对象有
.exists()
,若QuerySet为空,返回False,若QuerySet非空,则返回True。 - QuerySet.count()可以返回结果数量
- QuerySet可以被切片,但是不支持负索引。
表关系
一对多
class django.db.models.ForeignKey(to,on_delete,**options)
它有两个必填参数。to,指定所关联的 Model,它的中取值可以是直接引用其他的 Model,也可以是 Model 所对应的字符串名称;on_delete,当删除关联表的数据时,Django 将根据这个参数设定的值确定应该执行什么样的 SQL 约束。
on_delete 可以理解为 MySQL 外键的级联动作,当主表执行删除操作时对子表的影响,即子表要执行的操作,Django 提供的可选值如下所示(都在django.db.models
):
- CASCADE,级联删除,它是大部分 ForeignKey 的定义时选择的约束。它的表现是删除了“主”,则“子”也会被自动删除。
- PROTECT,删除被引用对象时,将会抛出 ProtectedError 异常。当主表被一个或多个子表关联时,主表被删除则会抛出异常。
- SET_NULL,设置删除对象所关联的外键字段为 null,但前提是设置了选项 null 为True,否则会抛出异常。
- SET_DEFAULT:将外键字段设置为默认值,但前提是设置了 default 选项,且指向的对象是存在的。
- SET(value):删除被引用对象时,设置外键字段为 value。value 如果是一个可调用对象,那么就会被设置为调用后的结果。
- DO_NOTHING:不做任何处理。但是,由于数据表之间存在引用关系,删除关联数据,会造成数据库抛出异常。
可选参数:
- to_field:关联对象的字段名称。默认情况下,Django 使用关联对象的主键(大部分情况下是 id),如果需要修改成其他字段,可以设置这个参数。但是,需要注意,能够关联的字段必须有 unique=True 的约束。
- db_constraint:默认值是 True,它会在数据库中创建外键约束,维护数据完整性。通常情况下,这符合大部分场景的需求。如果数据库中存在一些历史遗留的无效数据,则可以将其设置为 False,这时就需要自己去维护关联关系的正确性了。
- related_name:这个字段设置的值用于反向查询,默认不需要设置,Django 会设置其为
“模型类名小写 _set”
。 - related_query_name:这个名称用于反向过滤。如果设置了 related_name,那么将用它作为默认值,否则 Django 会把模型的名称作为默认值
比如:
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=32)
author = models.ForeignKey("Author", on_delete=models.CASCADE)
class Author(models.Model):
name = models.CharField(max_length=16)
如何查询见上文的内容
多对多
多对多实质上就是两个一对多,他是通过第三张表来实现多对多关系的。
在django中使用ManyToManyField()来创建多对多关系。
根据django的原则,多对多字段只需要在其中一个表中定义即可,那么要在那个表中定义呢?举一个现实的例子,产品与配料表,一个产品有多个配料,而一种配料可以作为多个产品的配料。哪里作为多对多关系字段的存放点呢?建议使用我们认知的一样放,比如产品与配料就把字段放在配料表中。
django.db.models.ManyToManyField
的简单使用:
from django.db import models
class Ingredient(models.Model):
name = models.CharField(max_length=32)
product = models.ManyToManyField("Product")
class Meta:
verbose_name = "配料表"
class Product(models.Model):
name = models.CharField(max_length=32)
class Meta:
verbose_name = "产品表"
上面说过,多对多就是通过三张表建立起来的两个一对多,那么第三张表在哪里呢?
原来django会给我们自动创建第三张表,名称为应用名_定义字段的类的小写_另一个类的小写
。
自定义第三张表
由于django给我们创建的第三张表只有三个字段:id
,定义字段类_id
,另一类_id
,假如我们想要为多对多添加额外的数据时,就不好用了。所以下面介绍如何定义第三张表。
- 在ManyToManyField字段中添加through参数,用于指定第三张表
- 在第三张表的模型中,写两个外键
比如:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField() # 进组时间
invite_reason = models.CharField(max_length=64) # 邀请原因
以上表为例,讲述多对多的增删改查
# 增加
g = Group.objects.get(pk=1)
p = Person.objects.get(pk=1)
g.members.add(p)
# 清空
g = Group.objects.get(pk=1)
g.members.clear()
# 删除
g = Group.objects.get(pk=1)
p = Person.objects.get(pk=2)
g.members.remove(p)
# 重置关系
g = Group.objects.get(pk=1)
g.members.set([1, 2, 3, 4])
# 查找
# 跨表时
# 没定义第三表,字段为表一小写_表二小写
# 如在a表中定义多对多,关联b表
# 那么为:a_b
# 定义第三表时,则为ManyToManyField的字段名
# 正向查
res = Group.objects.all().values("members__id")
res = Group.objects.filter(members__id="1").values("pk")
# 反向查
p = Person.objects.filter(membership__group_id=1)
一对一
建立一对一关系,需要用到models.OneToOneField
数据类型。
需要指定表和on_delete参数。
一对一非常类似多对一关系,可以简单的通过模型的属性访问关联的模型。
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
不同之处在于反向查询的时候。一对一关系中的关联模型同样具有一个管理器对象,但是该管理器表示一个单一的对象而不是对象的集合:
e = Entry.objects.get(id=2)
e.entrydetail # 返回关联的EntryDetail对象
如果没有对象赋值给这个关系,Django将抛出一个DoesNotExist异常。