Skip to main content

模型实例引用

本文档描述了 Model API的详细信息。它基于 模型数据库查询 指南中提供的材料,因此您可能希望在阅读这些文档之前阅读并理解这些文档。

在本参考文献中,我们将使用 数据库查询指南 中提出的 示例Weblog模型

创建对象

要创建模型的新实例,只需像任何其他Python类一样实例化它:

class Model(**kwargs)[源代码]

关键字参数只是您在模型上定义的字段的名称。注意,实例化模型不会触及你的数据库;为此,你需要 save()

注解

您可能会通过覆盖 __init__ 方法来定制模型。但是,如果这样做,请注意不要更改调用签名,因为任何更改可能会阻止保存模型实例。不要覆盖 __init__,请尝试使用以下方法之一:

  1. 在模型类上添加一个类方法:

    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")
    
  2. 在自定义管理器上添加方法(通常是首选):

    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_namesvalues 的顺序相同。如果所有模型的字段都存在,那么 values 保证为 __init__() 期望它们的顺序。也就是说,实例可以由 cls(*values) 创建。如果任何字段被延迟,它们不会出现在 field_names 中。在这种情况下,请为每个缺少的字段分配一个 django.db.models.DEFERRED 值。

除了创建新模型,from_db() 方法必须在新实例的 _state 属性中设置 addingdb 标志。

下面是一个示例,显示如何记录从数据库加载的字段的初始值:

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
        ]
    new = 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() 调用。

Changed in Django 1.10:

在旧版本中,您可以通过咨询 cls._deferred 来检查是否所有字段都已加载。此属性被删除,django.db.models.DEFERRED 是新的。

从数据库刷新对象

如果从模型实例中删除字段,则再次访问该字段会从数据库重新加载值:

>>> obj = MyModel.objects.first()
>>> del obj.field
>>> obj.field  # Loads the field from the database
Changed in Django 1.10:

在旧版本中,访问删除的字段引发 AttributeError,而不是重新加载它。

Model.refresh_from_db(using=None, fields=None)[源代码]

如果需要从数据库重新加载模型的值,可以使用 refresh_from_db() 方法。当在没有参数的情况下调用此方法时,将完成以下操作:

  1. 模型的所有非延迟字段都更新为数据库中当前存在的值。

  2. 先前加载的关系的值不再有效的相关实例将从重新加载的实例中删除。例如,如果您有一个外键从重载的实例到另一个名为 Author 的模型,那么如果 obj.author_id != obj.author.idobj.author 将被抛弃,并且下次访问它时将被重新加载 obj.author_id 的值。

模型的唯一领域是从数据库中装载。如批注其他依赖于数据库的值不会重新加载。任何 @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(ExampleModel, self).refresh_from_db(using, fields, **kwargs)
Model.get_deferred_fields()[源代码]

一个帮助方法,返回一个包含当前为此模型延迟的所有字段的属性名称的集合。

验证对象

验证模型涉及三个步骤:

  1. 验证模型字段 - Model.clean_fields()

  2. 验证模型作为一个整体 - Model.clean()

  3. 验证字段唯一性 - 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_uniqueTrue)并且提出具有包含来自所有三个阶段的错误的 message_dict 属性的 ValidationError

可选的 exclude 参数可用于提供可以从验证和清除中排除的字段名称列表。 ModelForm 使用此参数排除表单中不存在的字段被验证,因为用户无法纠正所引发的任何错误。

请注意,当您调用模型的 save() 方法时,full_clean() 将自动调用 not。当您要为自己手动创建的模型运行一步模型验证时,您需要手动调用它。例如:

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 ugettext_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 ValidationError, NON_FIELD_ERRORS
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() 将检查您的模型的任何唯一约束。

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 b doesn't have an ID yet.
>>> b2.save()
>>> b2.id     # Returns the ID of your new object.

在调用 save() 之前,无法确定ID的值,因为该值由数据库计算,而不是由Django计算。

为了方便起见,每个模型都默认有一个名为 idAutoField,除非您在模型中的字段上明确指定了 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!

请参阅下面的 How Django knows to UPDATE vs. INSERT,了解这种情况的原因。

显式指定自动主键值对于批量保存对象最为有用,当您确信不会有主键冲突。

保存时会发生什么?

当您保存对象时,Django会执行以下步骤:

  1. 发出预保存信号。 发送 pre_save 信号,允许监听该信号的任何功能做某事。

  2. 预处理数据。 调用每个字段的 pre_save() 方法来执行所需的任何自动数据修改。例如,日期/时间字段覆盖 pre_save() 以实现 auto_now_addauto_now

  3. 准备数据库的数据。 要求每个字段的 get_db_prep_save() 方法在可以写入数据库的数据类型中提供其当前值。

    大多数字段不需要数据准备。简单的数据类型,如整数和字符串,是“准备写”作为一个Python对象。然而,更复杂的数据类型通常需要一些修改。

    例如,DateField 字段使用Python datetime 对象来存储数据。数据库不存储 datetime 对象,因此字段值必须转换为符合ISO的日期字符串以插入到数据库中。

  4. 将数据插入数据库。 预处理的,准备好的数据被组成一个SQL语句以插入到数据库中。

  5. 发出后保存信号。 发送 post_save 信号,允许监听该信号的任何功能做某事。

如何Django知道UPDATE与INSERT

您可能已经注意到,Django数据库对象使用相同的 save() 方法来创建和更改对象。 Django摘要需要使用 INSERTUPDATE SQL语句。具体来说,当你调用 save() 时,Django遵循这个算法:

  • 如果对象的主键属性设置为评估为 True 的值(即,非 None 或空字符串的值),Django将执行 UPDATE

  • 如果对象的主键属性是 not 集或如果 UPDATE 没有更新任何东西,Django执行 INSERT

在这里得到的是,你应该注意不要在保存新对象时明确指定主键值,如果您不能保证主键值未被使用。有关这种细微差别的更多信息,请参阅上面的 Explicitly specifying auto-primary-key values 和下面的 Forcing an INSERT or UPDATE

在Django 1.5和更早版本中,Django在设置主键属性时执行了 SELECT。如果 SELECT 找到一行,那么Django做了一个 UPDATE,否则它做了一个 INSERT。旧的算法导致在 UPDATE 情况下多一个查询。在一些罕见的情况下,数据库不报告行已更新,即使数据库包含对象主键值的行。一个例子是返回 NULL 的PostgreSQL ON UPDATE 触发器。在这种情况下,可以通过将 select_on_save 选项设置为 True 来还原为旧算法。

强制INSERT或UPDATE

在某些罕见的情况下,必须能够强制 save() 方法执行SQL INSERT,而不是回退到执行 UPDATE。或者反之亦然:如果可能,更新,但不插入新行。在这些情况下,您可以将 force_insert=Trueforce_update=True 参数传递到 save() 方法。显然,传递这两个参数是一个错误:你不能同时插入 and 更新!

很少需要使用这些参数。 Django几乎总是做正确的事情,并试图覆盖,将导致错误,难以追踪。此功能仅供高级使用。

使用 update_fields 将强制更新类似于 force_update

基于现有字段更新属性

有时,您需要在字段上执行简单的算术任务,例如递增或递减当前值。实现这一点的明显方法是做类似的事情:

>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold += 1
>>> product.save()

如果从数据库检索的旧 number_sold 值为10,则值11将写回数据库。

通过表达相对于原始字段值的更新,而不是作为新值的显式赋值,可以使该过程变得鲁棒,避免竞争条件,以及略快。 Django提供 F expressions 来执行这种相对更新。使用 F expressions,前面的例子表示为:

>>> from django.db.models import F
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold = F('number_sold') + 1
>>> product.save()

有关更多详细信息,请参阅有关 F expressions 及其 在更新查询中使用 的文档。

指定要保存的字段

如果 save() 传递了关键字参数 update_fields 中的字段名称列表,则只会更新该列表中命名的字段。如果你想更新一个对象上的一个或几个字段,这可能是可取的。防止在数据库中更新所有模型字段将有轻微的性能优势。例如:

product.name = 'Name changed again'
product.save(update_fields=['name'])

update_fields 参数可以是任何包含字符串的iterable。空的 update_fields iterable将跳过保存。值为None将对所有字段执行更新。

指定 update_fields 将强制更新。

当保存通过延迟模型加载(only()defer())获取的模型时,只有从数据库加载的字段才会更新。实际上,在这种情况下存在自动 update_fields。如果分配或更改任何延迟字段值,该字段将添加到更新的字段。

删除对象

Model.delete(using=DEFAULT_DB_ALIAS, keep_parents=False)[源代码]

为对象发出SQL DELETE。这只会删除数据库中的对象; Python实例仍然存在,并且仍然在其字段中有数据。此方法返回删除的对象的数量和具有每个对象类型的删除数量的字典。

有关更多详细信息(包括如何批量删除对象),请参阅 删除对象

如果您想要自定义删除行为,您可以覆盖 delete() 方法。有关详细信息,请参阅 覆盖预定义的模型方法

有时使用 多表继承,您可能只想删除子模型数据。指定 keep_parents=True 将保留父模型的数据。

Changed in Django 1.9:

添加了 keep_parents 参数。

Changed in Django 1.9:

添加了描述删除对象数的返回值。

酸洗对象

当你 pickle 一个模型,它的当前状态被pickled。当取消选中它时,它将包含模型实例在它被pickled的时刻,而不是当前在数据库中的数据。

你不能在版本之间共享泡菜

酱菜模型只对用于生成它们的Django版本有效。如果你使用Django版本N生成一个pickle,不能保证pickle可以用Django版本N + 1读取。酱菜不应该用作长期档案策略的一部分。

由于pickle兼容性错误可能很难诊断,例如静默损坏的对象,当您尝试解开一个Django版本不同于它被酸洗的模型时,RuntimeWarning 被引发。

其他模型实例方法

一些对象方法有特殊用途。

__str__()

Model.__str__()[源代码]

每当您在对象上调用 str() 时,将调用 __str__() 方法。 Django在许多地方使用 str(obj)。最值得注意的是,在Django管理站点中显示对象,以及在显示对象时作为插入到模板中的值。因此,你应该总是从 __str__() 方法返回一个漂亮,人类可读的模型表示。

例如:

from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible  # only if you need to support Python 2
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)

如果你想与Python 2兼容,你可以使用 python_2_unicode_compatible() 来装饰模型类,如上所示。

__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)
# Primay 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__() 方法将在保存实例之前和之后返回不同的值,但在Python中禁止更改实例的 __hash__() 值。

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() 的一个地方在管理应用程序中。如果一个对象定义了这个方法,对象编辑页面将有一个“View on site”链接,它将直接跳转到对象的公共视图,如 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 所需),并且必要时进行URL编码。

调用 get_absolute_url() 的代码和模板应该能够直接使用结果,而无需任何进一步的处理。如果您使用的unicode字符串包含超出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 的每个 DateFieldDateTimeField,对象将具有 get_next_by_FOO()get_previous_by_FOO() 方法,其中 FOO 是字段的名称。这将返回相对于日期字段的下一个和上一个对象,在适当时引发 DoesNotExist 异常。

这两种方法都将使用模型的默认管理器执行查询。如果需要模拟自定义管理器使用的过滤,或者想要执行一次性自定义过滤,则这两种方法都还接受可选的关键字参数,它们应采用 字段查找 中描述的格式。

注意,在相同的日期值的情况下,这些方法将使用主键作为tie-breaker。这保证没有记录被跳过或重复。这也意味着你不能使用这些方法对未保存的对象。

其他属性

DoesNotExist

exception Model.DoesNotExist

ORM在几个地方引发此异常,例如,当找不到给定查询参数的对象时,由 QuerySet.get() 引发。

Django提供了一个 DoesNotExist 异常作为每个模型类的属性来标识无法找到的对象类,并允许您使用 try/except 捕获特定的模型类。异常是 django.core.exceptions.ObjectDoesNotExist 的子类。