模型实例参考 — Django 文档
模型实例参考
本文档描述了 Model
API 的详细信息。 它建立在 模型 和 数据库查询 指南中提供的材料之上,因此您可能希望在阅读本文之前先阅读并理解这些文档。
在本参考文献中,我们将使用 数据库查询指南 中介绍的 示例网络日志模型 。
创建对象
要创建一个新的模型实例,像其他 Python 类一样实例化它。
- class Model(**kwargs)
关键字参数只是您在模型上定义的字段的名称。 请注意,实例化模型绝不会触及您的数据库; 为此,您需要 save()。
笔记
您可能会想通过覆盖 __init__
方法来自定义模型。 但是,如果这样做,请注意不要更改调用签名,因为任何更改都可能会阻止保存模型实例。 不要覆盖 __init__
,而是尝试使用以下方法之一:
在模型类上添加一个类方法:
from django.db import models class Book(models.Model): title = models.CharField(max_length=100) @classmethod def create(cls, title): book = cls(title=title) # do something with the book return book book = Book.create("Pride and Prejudice")
在自定义管理器上添加一个方法(通常首选):
class BookManager(models.Manager): def create_book(self, title): book = self.create(title=title) # do something with the book return book class Book(models.Model): title = models.CharField(max_length=100) objects = BookManager() book = Book.objects.create_book("Pride and Prejudice")
自定义模型加载
- classmethod Model.from_db(db, field_names, values)
从数据库加载时,from_db()
方法可用于自定义模型实例创建。
db
参数包含加载模型的数据库的数据库别名,field_names
包含所有加载字段的名称,values
包含每个字段的加载值field_names
。 field_names
与 values
的顺序相同。 如果模型的所有字段都存在,则 values
保证按 __init__()
期望的顺序排列。 即实例可以通过cls(*values)
创建。 如果任何字段被延迟,它们将不会出现在 field_names
中。 在这种情况下,为每个缺失的字段分配值 django.db.models.DEFERRED
。
除了创建新模型外,from_db()
方法必须在新实例的 _state
属性中设置 adding
和 db
标志。
下面是一个示例,显示如何记录从数据库加载的字段的初始值:
from django.db.models import DEFERRED
@classmethod
def from_db(cls, db, field_names, values):
# Default implementation of from_db() (subject to change and could
# be replaced with super()).
if len(values) != len(cls._meta.concrete_fields):
values = list(values)
values.reverse()
values = [
values.pop() if f.attname in field_names else DEFERRED
for f in cls._meta.concrete_fields
]
instance = cls(*values)
instance._state.adding = False
instance._state.db = db
# customization to store the original field values on the instance
instance._loaded_values = dict(zip(field_names, values))
return instance
def save(self, *args, **kwargs):
# Check how the current values differ from ._loaded_values. For example,
# prevent changing the creator_id of the model. (This example doesn't
# support cases where 'creator_id' is deferred).
if not self._state.adding and (
self.creator_id != self._loaded_values['creator_id']):
raise ValueError("Updating the value of creator isn't allowed")
super().save(*args, **kwargs)
上面的例子展示了一个完整的 from_db()
实现来阐明它是如何完成的。 在这种情况下,当然可以只在 from_db()
方法中使用 super()
调用。
从数据库中刷新对象
如果您从模型实例中删除一个字段,再次访问它会重新加载数据库中的值:
>>> obj = MyModel.objects.first()
>>> del obj.field
>>> obj.field # Loads the field from the database
- Model.refresh_from_db(using=None, fields=None)
如果需要从数据库重新加载模型的值,可以使用 refresh_from_db()
方法。 当不带参数调用此方法时,将执行以下操作:
- 模型的所有非递延字段都更新为数据库中当前的值。
- 任何缓存的关系都会从重新加载的实例中清除。
只有模型的字段会从数据库中重新加载。 其他依赖于数据库的值(例如注释)不会重新加载。 任何 @cached_property 属性也不会被清除。
重新加载是从加载实例的数据库中进行的,如果实例不是从数据库中加载的,则从默认数据库中进行。 using
参数可用于强制用于重新加载的数据库。
可以使用 fields
参数强制加载字段集。
例如,要测试 update()
调用是否导致预期的更新,您可以编写类似于以下内容的测试:
def test_update_result(self):
obj = MyModel.objects.create(val=1)
MyModel.objects.filter(pk=obj.pk).update(val=F('val') + 1)
# At this point obj.val is still 1, but the value in the database
# was updated to 2. The object's updated value needs to be reloaded
# from the database.
obj.refresh_from_db()
self.assertEqual(obj.val, 2)
请注意,当访问延迟字段时,延迟字段的值的加载通过此方法发生。 因此,可以自定义延迟加载的方式。 下面的示例显示了如何在重新加载延迟字段时重新加载所有实例的字段:
class ExampleModel(models.Model):
def refresh_from_db(self, using=None, fields=None, **kwargs):
# fields contains the name of the deferred field to be
# loaded.
if fields is not None:
fields = set(fields)
deferred_fields = self.get_deferred_fields()
# If any deferred field is going to be loaded
if fields.intersection(deferred_fields):
# then load all of them
fields = fields.union(deferred_fields)
super().refresh_from_db(using, fields, **kwargs)
- Model.get_deferred_fields()
一个辅助方法,返回一个包含当前这个模型的所有这些字段的属性名的集合。
验证对象
验证一个模型有三个步骤:
- 验证模型字段 - Model.clean_fields()
- 整体验证模型 - Model.clean()
- 验证字段唯一性 - Model.validate_unique()
当您调用模型的 full_clean() 方法时,将执行所有三个步骤。
当您使用 ModelForm 时,对 is_valid() 的调用将对表单中包含的所有字段执行这些验证步骤。 有关更多信息,请参阅 ModelForm 文档 。 如果您计划自己处理验证错误,或者您已经从 ModelForm 中排除了需要验证的字段,则您应该只需要调用模型的 full_clean() 方法。
- Model.full_clean(exclude=None, validate_unique=True)
此方法调用 Model.clean_fields()、Model.clean() 和 Model.validate_unique()(如果 validate_unique
是 True
),按该顺序并引发 ValidationError,该属性具有 message_dict
属性,其中包含来自所有三个阶段的错误。
可选的 exclude
参数可用于提供可从验证和清理中排除的字段名称列表。 ModelForm 使用此参数来排除表单中不存在的字段,因为用户无法更正引发的任何错误。
请注意,当您调用模型的 save() 方法时,full_clean()
将 不会 被自动调用。 当您想为自己手动创建的模型运行一步模型验证时,您需要手动调用它。 例如:
from django.core.exceptions import ValidationError
try:
article.full_clean()
except ValidationError as e:
# Do something based on the errors contained in e.message_dict.
# Display them to a user, or handle them programmatically.
pass
full_clean()
执行的第一步是清理每个单独的字段。
- Model.clean_fields(exclude=None)
此方法将验证模型上的所有字段。 可选的 exclude
参数允许您提供要从验证中排除的字段名称列表。 如果任何字段验证失败,它将引发 ValidationError。
full_clean()
执行的第二步是调用 Model.clean()。 应该重写此方法以对您的模型执行自定义验证。
- Model.clean()
此方法应用于提供自定义模型验证,并根据需要修改模型上的属性。 例如,您可以使用它自动为字段提供值,或者进行需要访问多个字段的验证:
import datetime
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
class Article(models.Model):
...
def clean(self):
# Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None:
raise ValidationError(_('Draft entries may not have a publication date.'))
# Set the pub_date for published items if it hasn't been set already.
if self.status == 'published' and self.pub_date is None:
self.pub_date = datetime.date.today()
但是请注意,与 Model.full_clean() 一样,当您调用模型的 save() 方法时,不会调用模型的 clean()
方法。
在上面的例子中,由 Model.clean()
引发的 ValidationError 异常是用字符串实例化的,因此它将存储在一个特殊的错误字典键中,NON_FIELD_ERRORS。 此键用于与整个模型相关的错误,而不是与特定字段相关的错误:
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
try:
article.full_clean()
except ValidationError as e:
non_field_errors = e.message_dict[NON_FIELD_ERRORS]
要将异常分配给特定字段,请使用字典实例化 ValidationError,其中键是字段名称。 我们可以更新前面的示例,将错误分配给 pub_date
字段:
class Article(models.Model):
...
def clean(self):
# Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None:
raise ValidationError({'pub_date': _('Draft entries may not have a publication date.')})
...
如果在 Model.clean()
期间检测到多个字段中的错误,还可以传递一个字典映射字段名称给错误:
raise ValidationError({
'title': ValidationError(_('Missing title.'), code='required'),
'pub_date': ValidationError(_('Invalid date.'), code='invalid'),
})
最后,full_clean()
将检查您的模型上的任何唯一约束。
如果这些字段未出现在 ModelForm
中,如何引发特定于字段的验证错误
对于未出现在模型表单中的字段,您不能在 Model.clean()
中引发验证错误(表单可能会使用 Meta.fields
或 Meta.exclude
限制其字段)。 这样做会引发 ValueError
,因为验证错误将无法与排除的字段相关联。
要解决此难题,请改写 Model.clean_fields(),因为它接收从验证中排除的字段列表。 例如:
class Article(models.Model):
...
def clean_fields(self, exclude=None):
super().clean_fields(exclude=exclude)
if self.status == 'draft' and self.pub_date is not None:
if exclude and 'status' in exclude:
raise ValidationError(
_('Draft entries may not have a publication date.')
)
else:
raise ValidationError({
'status': _(
'Set status to draft if there is not a '
'publication date.'
),
})
- Model.validate_unique(exclude=None)
此方法类似于 clean_fields(),但验证模型上的所有唯一性约束而不是单个字段值。 可选的 exclude
参数允许您提供要从验证中排除的字段名称列表。 如果任何字段验证失败,它将引发 ValidationError。
请注意,如果您向 validate_unique()
提供 exclude
参数,则不会检查涉及您提供的字段之一的任何 unique_together 约束。
保存对象
要将对象保存回数据库,请调用 save()
:
- Model.save(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)
如果您想要自定义保存行为,您可以覆盖此 save()
方法。 有关更多详细信息,请参阅 覆盖预定义模型方法 。
模型保存过程也有一些微妙的地方,请看下面的章节。
自增主键
如果一个模型有一个 AutoField——一个自动递增的主键——那么在你第一次调用 save()
时,这个自动递增的值将被计算并保存为对象的一个属性:
>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b2.id # Returns None, because b2 doesn't have an ID yet.
>>> b2.save()
>>> b2.id # Returns the ID of your new object.
在您调用 save()
之前,无法判断 ID 的值是多少,因为该值是由您的数据库计算的,而不是由 Django 计算的。
为方便起见,默认情况下每个模型都有一个名为 id
的 AutoField,除非您在模型的字段上明确指定 primary_key=True
。 有关更多详细信息,请参阅 AutoField 的文档。
pk 属性
- Model.pk
不管你自己定义一个主键字段,还是让 Django 为你提供一个,每个模型都会有一个名为 pk
的属性。 它的行为类似于模型上的普通属性,但实际上是模型主键字段属性的别名。 您可以读取和设置此值,就像您对任何其他属性所做的一样,它将更新模型中的正确字段。
明确指定自动主键值
如果模型有 AutoField 但你想在保存时显式定义新对象的 ID,只需在保存前显式定义它,而不是依赖于 ID 的自动分配:
>>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b3.id # Returns 3.
>>> b3.save()
>>> b3.id # Returns 3.
如果您手动分配自动主键值,请确保不要使用已经存在的主键值! 如果您使用数据库中已存在的显式主键值创建新对象,Django 将假定您正在更改现有记录而不是创建新记录。
鉴于上面的 'Cheddar Talk'
博客示例,此示例将覆盖数据库中的先前记录:
b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
b4.save() # Overrides the previous blog with ID=3!
看 Django 如何知道更新与更新 插入 ,下面,这是发生这种情况的原因。
当您确信不会发生主键冲突时,显式指定自动主键值对于批量保存对象最有用。
如果您使用的是 PostgreSQL,则可能需要更新与主键关联的序列; 请参阅 手动指定自动递增主键的值 。
保存时会发生什么?
当你保存一个对象时,Django 会执行以下步骤:
发出预保存信号。 发送 pre_save 信号,允许侦听该信号的任何函数执行某些操作。
预处理数据。每个字段的pre_save()方法被调用来执行任何需要的自动数据修改。 例如,日期/时间字段覆盖
pre_save()
以实现 auto_now_add 和 auto_now。为数据库准备数据。 每个字段的 get_db_prep_save() 方法被要求以可写入数据库的数据类型提供其当前值。
大多数字段不需要数据准备。 简单的数据类型,例如整数和字符串,都可以作为 Python 对象“随时写入”。 但是,更复杂的数据类型通常需要进行一些修改。
例如,DateField 字段使用 Python
datetime
对象来存储数据。 数据库不存储datetime
对象,因此必须将字段值转换为符合 ISO 标准的日期字符串才能插入到数据库中。将数据插入数据库。 预处理、准备好的数据组成SQL语句插入数据库。
发出保存后信号。 发送 post_save 信号,允许侦听该信号的任何函数执行某些操作。
Django 如何知道更新与更新 插入
您可能已经注意到 Django 数据库对象使用相同的 save()
方法来创建和更改对象。 Django 抽象了使用 INSERT
或 UPDATE
SQL 语句的需要。 具体来说,当你调用 save()
时,Django 遵循这个算法:
- 如果对象的主键属性设置为计算结果为
True
的值(即,除None
或空字符串以外的值),Django 将执行UPDATE
。 - 如果对象的主键属性设置为 未设置 或者
UPDATE
未更新任何内容(例如 如果主键设置为数据库中不存在的值),Django 将执行INSERT
。
这里的一个问题是,如果您不能保证主键值未使用,则在保存新对象时应注意不要显式指定主键值。 有关此细微差别的更多信息,请参阅上面的 显式指定自动主键值 和下面的 强制插入或更新 。
在 Django 1.5 及更早版本中,Django 在设置主键属性时执行了 SELECT
。 如果 SELECT
找到一行,则 Django 执行 UPDATE
,否则执行 INSERT
。 在 UPDATE
的情况下,旧算法会导致多一个查询。 在极少数情况下,即使数据库包含对象主键值的行,数据库也不会报告行已更新。 一个例子是 PostgreSQL ON UPDATE
触发器,它返回 NULL
。 在这种情况下,可以通过将 select_on_save 选项设置为 True
来恢复到旧算法。
基于现有字段更新属性
有时您需要对字段执行简单的算术任务,例如递增或递减当前值。 实现这一目标的显而易见的方法是执行以下操作:
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold += 1
>>> product.save()
如果从数据库中检索到的旧 number_sold
值为 10,则值 11 将被写回数据库。
该过程可以变得健壮, 避免竞争条件 ,并且通过表达相对于原始字段值的更新,而不是作为新值的显式分配来稍微快一点。 Django 提供了 F 表达式 来执行这种相对更新。 使用F表达式,前面的例子表示为:
>>> from django.db.models import F
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold = F('number_sold') + 1
>>> product.save()
有关更多详细信息,请参阅有关 F 表达式 及其 在更新查询中的使用 的文档。
指定要保存的字段
如果 save()
在关键字参数 update_fields
中传递了一个字段名称列表,则只会更新该列表中命名的字段。 如果您只想更新对象上的一个或几个字段,这可能是可取的。 阻止所有模型字段在数据库中更新会带来轻微的性能优势。 例如:
product.name = 'Name changed again'
product.save(update_fields=['name'])
update_fields
参数可以是任何包含字符串的可迭代对象。 空的 update_fields
可迭代将跳过保存。 None 值将对所有字段执行更新。
指定 update_fields
将强制更新。
当保存通过延迟模型加载(only() 或 defer())获取的模型时,只有从数据库加载的字段才会更新。 在这种情况下,实际上有一个自动 update_fields
。 如果您分配或更改任何延迟字段值,该字段将添加到更新的字段中。
删除对象
- Model.delete(using=DEFAULT_DB_ALIAS, keep_parents=False)
为对象发出 SQL DELETE
。 这只会删除数据库中的对象; Python 实例仍将存在,并且其字段中仍将有数据。 此方法返回删除的对象数和包含每个对象类型删除数的字典。
有关更多详细信息,包括如何批量删除对象,请参阅 删除对象 。
如果您想要自定义删除行为,您可以覆盖 delete()
方法。 有关更多详细信息,请参阅 覆盖预定义模型方法 。
有时使用 多表继承 您可能只想删除子模型的数据。 指定 keep_parents=True
将保留父模型的数据。
Pickle 序列化对象
当你pickle
一个模型时,它的当前状态是pickled。 当您解压它时,它将包含在它被酸洗时的模型实例,而不是当前在数据库中的数据。
其他模型实例方法
有几个对象方法有特殊用途。
__str__()
- Model.__str__()
每当您在对象上调用 str()
时,都会调用 __str__()
方法。 Django 在许多地方使用 str(obj)
。 最值得注意的是,在 Django 管理站点中显示一个对象,并在它显示一个对象时作为插入到模板中的值。 因此,您应该始终从 __str__()
方法返回一个漂亮的、人类可读的模型表示。
例如:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
__eq__()
- Model.__eq__()
相等方法被定义为具有相同主键值和相同具体类的实例被认为是相等的,除了主键值为 None
的实例不等于除自身之外的任何东西。 对于代理模型,具体类被定义为模型的第一个非代理父类; 对于所有其他模型,它只是模型的类。
例如:
from django.db import models
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
class MyProxyModel(MyModel):
class Meta:
proxy = True
class MultitableInherited(MyModel):
pass
# Primary keys compared
MyModel(id=1) == MyModel(id=1)
MyModel(id=1) != MyModel(id=2)
# Primary keys are None
MyModel(id=None) != MyModel(id=None)
# Same instance
instance = MyModel(id=None)
instance == instance
# Proxy model
MyModel(id=1) == MyProxyModel(id=1)
# Multi-table inheritance
MyModel(id=1) != MultitableInherited(id=1)
__hash__()
- Model.__hash__()
__hash__()
方法基于实例的主键值。 它实际上是 hash(obj.pk)
。 如果实例没有主键值,则将引发 TypeError
(否则 __hash__()
方法将在保存实例前后返回不同的值,但更改 __hash__()
实例的值在 Python 中是被禁止的。
get_absolute_url()
- Model.get_absolute_url()
定义一个 get_absolute_url()
方法来告诉 Django 如何计算对象的规范 URL。 对于调用者来说,此方法应该返回一个字符串,该字符串可用于通过 HTTP 引用对象。
例如:
def get_absolute_url(self):
return "/people/%i/" % self.id
虽然这段代码是正确和简单的,但它可能不是编写这种方法的最可移植的方式。 reverse() 函数通常是最好的方法。
例如:
def get_absolute_url(self):
from django.urls import reverse
return reverse('people.views.details', args=[str(self.id)])
Django 使用 get_absolute_url()
的一个地方是在管理应用程序中。 如果对象定义了此方法,对象编辑页面将有一个“现场查看”链接,该链接将直接跳转到对象的公共视图,如 get_absolute_url()
所给出的。
类似地,Django 的一些其他部分,例如 联合提要框架 ,在定义时使用 get_absolute_url()
。 如果你的模型实例每个都有一个唯一的 URL,你应该定义 get_absolute_url()
。
警告
您应该避免从未经验证的用户输入构建 URL,以减少链接或重定向中毒的可能性:
def get_absolute_url(self):
return '/%s/' % self.name
如果 self.name
是 '/example.com'
,则返回 '//example.com/'
,后者是有效的架构相对 URL,但不是预期的 '/%2Fexample.com/'
。
在模板中使用 get_absolute_url()
是一种很好的做法,而不是硬编码对象的 URL。 例如,这个模板代码很糟糕:
<!-- BAD template code. Avoid! -->
<a href="/people/{{ object.id }}/">{{ object.name }}</a>
这个模板代码就好多了:
<a href="{{ object.get_absolute_url }}">{{ object.name }}</a>
这里的逻辑是,如果您更改对象的 URL 结构,即使是更正拼写错误等简单的事情,您也不希望跟踪可能创建 URL 的每个位置。 在 get_absolute_url()
中指定一次,然后让所有其他代码调用该位置。
笔记
您从 get_absolute_url()
返回的字符串必须 只包含 ASCII 字符(URI 规范要求,RFC 2396#section-2)并且是URL 编码,如有必要。
调用 get_absolute_url()
的代码和模板应该能够直接使用结果而无需任何进一步处理。 如果您使用的字符串包含 ASCII 范围之外的字符,您可能希望使用 django.utils.encoding.iri_to_uri()
函数来帮助解决此问题。
额外的实例方法
除了 save()、delete() 之外,模型对象可能还有以下一些方法:
- Model.get_FOO_display()
对于每个设置了 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=2, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
- Model.get_next_by_FOO(\**kwargs)
- Model.get_previous_by_FOO(\**kwargs)
对于每个没有 null=True 的 DateField 和 DateTimeField,对象将具有 get_next_by_FOO()
和 get_previous_by_FOO()
方法,其中 FOO
是字段的名称。 这将返回关于日期字段的下一个和上一个对象,并在适当时引发 DoesNotExist 异常。
这两种方法都将使用模型的默认管理器执行查询。 如果您需要模拟自定义管理器使用的过滤,或者想要执行一次性自定义过滤,这两种方法还接受可选的关键字参数,其格式应在 字段查找 中描述。
请注意,在日期值相同的情况下,这些方法将使用主键作为决胜局。 这保证没有记录被跳过或重复。 这也意味着您不能在未保存的对象上使用这些方法。
其他属性
DoesNotExist
- exception Model.DoesNotExist
这个异常是由 ORM 在几个地方引发的,例如 QuerySet.get() 当找不到给定查询参数的对象时。
Django 提供了一个
DoesNotExist
异常作为每个模型类的属性,以识别无法找到的对象的类,并允许您使用try/except
捕获特定的模型类。 例外是 django.core.exceptions.ObjectDoesNotExist 的子类。