模型实例参考

本文档描述了 Model API 的细节。它建立在 模型数据库查询 指南中所介绍的材料基础上,因此,在阅读本文档之前,你可能需要阅读并理解这些文档。

在整个参考资料中,我们将使用在 数据库查询指南 中提出的 示例博客模型

创建对象

要创建一个新的模型实例,像其他 Python 类一样实例化它。

class Model(**kwargs)

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

备注

你可能会尝试通过覆盖 __init__ 方法来自定义模型。然而,如果这样做,请小心不要更改调用签名,因为任何更改都可能阻止模型实例被保存。此外,在 __init__ 中引用模型字段可能在某些情况下导致无限递归错误。而不是覆盖 __init__,可以尝试使用以下方法之一:

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

    ``` from django.db import models

  1. class Book(models.Model):
  2. title = models.CharField(max_length=100)
  3. @classmethod
  4. def create(cls, title):
  5. book = cls(title=title)
  6. # do something with the book
  7. return book
  8. book = Book.create("Pride and Prejudice")
  9. ```
  1. 在自定义管理器上添加一个方法(通常首选):

    ``` class BookManager(models.Manager):

    1. def create_book(self, title):
    2. book = self.create(title=title)
    3. # do something with the book
    4. return book
  1. class Book(models.Model):
  2. title = models.CharField(max_length=100)
  3. objects = BookManager()
  4. book = Book.objects.create_book("Pride and Prejudice")
  5. ```

自定义模型加载

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 标志。

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

  1. from django.db.models import DEFERRED
  2. @classmethod
  3. def from_db(cls, db, field_names, values):
  4. # Default implementation of from_db() (subject to change and could
  5. # be replaced with super()).
  6. if len(values) != len(cls._meta.concrete_fields):
  7. values = list(values)
  8. values.reverse()
  9. values = [
  10. values.pop() if f.attname in field_names else DEFERRED
  11. for f in cls._meta.concrete_fields
  12. ]
  13. instance = cls(*values)
  14. instance._state.adding = False
  15. instance._state.db = db
  16. # customization to store the original field values on the instance
  17. instance._loaded_values = dict(
  18. zip(field_names, (value for value in values if value is not DEFERRED))
  19. )
  20. return instance
  21. def save(self, *args, **kwargs):
  22. # Check how the current values differ from ._loaded_values. For example,
  23. # prevent changing the creator_id of the model. (This example doesn't
  24. # support cases where 'creator_id' is deferred).
  25. if not self._state.adding and (
  26. self.creator_id != self._loaded_values["creator_id"]
  27. ):
  28. raise ValueError("Updating the value of creator isn't allowed")
  29. super().save(*args, **kwargs)

上面的例子显示了一个完整的 from_db() 实现,以说明如何做到这一点。在这种情况下,可以在 from_db() 方法中使用 super() 调用。

从数据库中刷新对象

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

  1. >>> obj = MyModel.objects.first()
  2. >>> del obj.field
  3. >>> obj.field # Loads the field from the database

Model.refresh_from_db(using=None, fields=None)

Model.arefresh_from_db(using=None, fields=None)

异步版本: arefresh_from_db()

如果你需要从数据库中重新加载一个模型的值,你可以使用 refresh_from_db() 方法。当这个方法被调用时,没有参数时,会做以下工作:

  1. 模型的所有非递延字段都更新为数据库中当前的值。
  2. 任何缓存的关系都会从重新加载的实例中清除。

只有模型的字段会从数据库中重载。其他依赖于数据库的值,如注释,不会被重载。任何 @cached_property 属性也不会被清除。

重载发生在实例被加载的数据库中,如果实例不是从数据库中加载的,则从默认数据库中加载。using 参数可以用来强制使用数据库进行重载。

可以通过使用 fields 参数强制加载一组字段。

例如,为了测试 update() 的调用是否导致了预期的更新,你可以写一个类似这样的测试:

  1. def test_update_result(self):
  2. obj = MyModel.objects.create(val=1)
  3. MyModel.objects.filter(pk=obj.pk).update(val=F("val") + 1)
  4. # At this point obj.val is still 1, but the value in the database
  5. # was updated to 2. The object's updated value needs to be reloaded
  6. # from the database.
  7. obj.refresh_from_db()
  8. self.assertEqual(obj.val, 2)

请注意,当访问递延字段时,递延字段的值的加载是通过这个方法发生的。因此,可以自定义递延加载的发生方式。下面的例子显示了当一个递延字段被重载时,如何重载实例的所有字段:

  1. class ExampleModel(models.Model):
  2. def refresh_from_db(self, using=None, fields=None, **kwargs):
  3. # fields contains the name of the deferred field to be
  4. # loaded.
  5. if fields is not None:
  6. fields = set(fields)
  7. deferred_fields = self.get_deferred_fields()
  8. # If any deferred field is going to be loaded
  9. if fields.intersection(deferred_fields):
  10. # then load all of them
  11. fields = fields.union(deferred_fields)
  12. super().refresh_from_db(using, fields, **kwargs)

Model.get_deferred_fields()

一个辅助方法,返回一个包含当前这个模型的所有这些字段的属性名的集合。

Changed in Django 4.2:

已添加了 arefresh_from_db() 方法。

验证对象

验证模型涉及四个步骤:

  1. 验证模型字段—— Model.clean_fields()
  2. 验证整个模型—— Model.clean()
  3. 验证字段的唯一性—— Model.validate_unique()
  4. 验证约束 - Model.validate_constraints()

当你调用模型的 full_clean() 方法时,会执行所有四个步骤。

当你使用一个 ModelForm 时,调用 is_valid() 将对表单中包含的所有字段执行这些验证步骤。更多信息请参见 模型表单文档。只有当你打算自己处理验证错误,或者你从 ModelForm 中排除了需要验证的字段时,才需要调用模型的 full_clean() 方法。

警告

包含 JSONField 的约束可能不会引发验证错误,因为键、索引和路径转换具有许多与数据库相关的注意事项。这个问题 可能在以后被完全支持

您应该始终检查是否有来自 django.db.models 记录器的日志消息,例如 “在…上调用 check() 时出现了数据库错误”,以确认它已正确验证。

Changed in Django 4.1:

在早期版本中,模型验证过程中不会检查约束。

Model.full_clean(exclude=None, validate_unique=True, validate_constraints=True)

该方法按照以下顺序调用 Model.clean_fields()Model.clean()Model.validate_unique() (如果 validate_uniqueTrue)、以及 Model.validate_constraints() (如果 validate_constraintsTrue),并引发一个具有包含来自所有四个阶段的错误的 ValidationError,其中包含了 message_dict 属性。

可选的 exclude 参数可以用来提供一个字段名称的 set,可以从验证和清理中排除。 ModelForm 使用这个参数来排除在表单上不存在的字段,因为用户无法纠正引发的任何错误。

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

  1. from django.core.exceptions import ValidationError
  2. try:
  3. article.full_clean()
  4. except ValidationError as e:
  5. # Do something based on the errors contained in e.message_dict.
  6. # Display them to a user, or handle them programmatically.
  7. pass

full_clean() 执行的第一步是清理每个单独的字段。

Changed in Django 4.1:

添加了 validate_constraints 参数。

Changed in Django 4.1:

exclude 值现在转换为 set 而不是 list

Model.clean_fields(exclude=None)

该方法将验证模型上的所有字段。可选的 exclude 参数允许您提供一个字段名称的 set,以排除验证。如果任何字段未通过验证,它将引发 ValidationError

full_clean() 执行的第二步是调用 Model.clean()。这个方法应该被重写,以便对你的模型进行自定义验证。

Model.clean()

这个方法应该用来提供自定义模型验证,如果需要的话,还可以修改模型上的属性。例如,你可以使用它来自动为一个字段提供一个值,或进行需要访问多个字段的验证:

  1. import datetime
  2. from django.core.exceptions import ValidationError
  3. from django.db import models
  4. from django.utils.translation import gettext_lazy as _
  5. class Article(models.Model):
  6. ...
  7. def clean(self):
  8. # Don't allow draft entries to have a pub_date.
  9. if self.status == "draft" and self.pub_date is not None:
  10. raise ValidationError(_("Draft entries may not have a publication date."))
  11. # Set the pub_date for published items if it hasn't been set already.
  12. if self.status == "published" and self.pub_date is None:
  13. self.pub_date = datetime.date.today()

但请注意,像 Model.full_clean() 一样,当你调用你的模型的 save() 方法时,模型的 clean() 方法不会被调用。

在上面的例子中,由 Model.clean() 引发的 ValidationError 异常是用字符串实例化的,所以它将被存储在一个特殊的错误字典键中, NON_FIELD_ERRORS。这个键用于与整个模型相关的错误,而不是与某个特定字段相关的错误:

  1. from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
  2. try:
  3. article.full_clean()
  4. except ValidationError as e:
  5. non_field_errors = e.message_dict[NON_FIELD_ERRORS]

要将异常分配给一个特定的字段,用一个字典实例化 ValidationError,其中键是字段名。我们可以更新前面的例子,将错误分配给 pub_date 字段:

  1. class Article(models.Model):
  2. ...
  3. def clean(self):
  4. # Don't allow draft entries to have a pub_date.
  5. if self.status == "draft" and self.pub_date is not None:
  6. raise ValidationError(
  7. {"pub_date": _("Draft entries may not have a publication date.")}
  8. )
  9. ...

如果你在 Model.clean() 期间检测到多个字段的错误,你也可以传递一个字段名与错误映射的字典:

  1. raise ValidationError(
  2. {
  3. "title": ValidationError(_("Missing title."), code="required"),
  4. "pub_date": ValidationError(_("Invalid date."), code="invalid"),
  5. }
  6. )

接下来,full_clean() 将检查模型上的唯一约束。

如果字段没有出现在 ModelForm 中,如何引发特定字段的验证错误。

你不能在 Model.clean() 中对没有出现在模型表单中的字段提出验证错误(一个表单可以使用 Meta.fieldMeta.exclude 来限制它的字段)。这样做会引发一个 ValueError,因为验证错误将无法与被排除的字段相关联。

为了解决这个难题,可以覆盖 Model.clean_fields(),因为它接收的是被排除在验证之外的字段列表。例如:

  1. class Article(models.Model):
  2. ...
  3. def clean_fields(self, exclude=None):
  4. super().clean_fields(exclude=exclude)
  5. if self.status == "draft" and self.pub_date is not None:
  6. if exclude and "status" in exclude:
  7. raise ValidationError(
  8. _("Draft entries may not have a publication date.")
  9. )
  10. else:
  11. raise ValidationError(
  12. {
  13. "status": _(
  14. "Set status to draft if there is not a " "publication date."
  15. ),
  16. }
  17. )

Model.validate_unique(exclude=None)

这个方法类似于 clean_fields(),但是验证模型上定义的唯一约束,而不是单个字段的值,这些约束通过 Field.uniqueField.unique_for_dateField.unique_for_monthField.unique_for_yearMeta.unique_together 来定义。可选的 exclude 参数允许您提供一个字段名称的 set,以排除验证。如果任何字段未通过验证,它将引发 ValidationError

Meta.constraints 中定义的 UniqueConstraintModel.validate_constraints() 进行验证。

请注意,如果你为 validate_unique() 提供了一个 exclude 参数,任何涉及你提供的一个字段的 unique_together 约束将不会被检查。

最后,full_clean() 将检查模型上的任何其他约束。

Changed in Django 4.1:

在旧版本中,UniqueConstraints 由 validate_unique() 进行验证。

Model.validate_constraints(exclude=None)

New in Django 4.1.

这个方法验证了在 Meta.constraints 中定义的所有约束。可选的 exclude 参数允许您提供一个字段名称的 set,以排除验证。如果任何约束未通过验证,它将引发 ValidationError

保存对象

要将对象保存回数据库,调用 save()

Model.save(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)

Model.asave(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)

异步版本: asave()

关于使用 force_insertforce_update 参数的细节,见 强制执行 INSERT 或 UPDATE 。关于 update_fields 参数的细节可以在 指定要保存的字段 部分找到。

如果你想自定义保存行为,你可以覆盖这个 save() 方法。更多细节请参见 重写之前定义的模型方法

模型保存过程也有一些微妙的地方,请看下面的章节。

Changed in Django 4.2:

已添加了 asave() 方法。

自增主键

如果模型具有 AutoField — 一个自增的主键 — 那么在第一次调用 save() 时,自增的值将被计算并保存为对象的属性:

  1. >>> b2 = Blog(name="Cheddar Talk", tagline="Thoughts on cheese.")
  2. >>> b2.id # Returns None, because b2 doesn't have an ID yet.
  3. >>> b2.save()
  4. >>> b2.id # Returns the ID of your new object.

在你调用 save() 之前,没有办法知道一个 ID 的值是多少,因为这个值是由你的数据库计算出来的,而不是由 Django 计算出来的。

为了方便起见,每个模型都有一个 AutoField 默认命名为 id,除非你在模型中的字段上明确指定 primary_key=True。更多细节请参见 AutoField 的文档。

pk 属性

Model.pk

无论你是自己定义一个主键字段,还是让 Django 为你提供一个主键字段,每个模型都会有一个叫做 pk 的属性。它的行为就像模型上的一个普通属性,但实际上是模型主键字段属性的别名。您可以像读取和设置任何其他属性一样读取和设置这个值,它将更新模型中的正确字段。

明确指定自动主键值

如果模型具有 AutoField,但你想在保存时明确地定义新对象的 ID,请在保存之前明确定义它,而不是依赖于自动分配的 ID:

  1. >>> b3 = Blog(id=3, name="Cheddar Talk", tagline="Thoughts on cheese.")
  2. >>> b3.id # Returns 3.
  3. >>> b3.save()
  4. >>> b3.id # Returns 3.

如果你手动分配自动主键值,请确保不要使用一个已经存在的主键值!如果你创建一个新的对象,并使用一个已经存在于数据库中的显式主键值,Django 会认为你是在改变现有的记录,而不是创建一个新的记录。

考虑到上面的 'Cheddar Talk' 博客的例子,这个例子将覆盖数据库中以前的记录:

  1. b4 = Blog(id=3, name="Not Cheddar", tagline="Anything but cheese.")
  2. b4.save() # Overrides the previous blog with ID=3!

发生这种情况的原因,请看下面 How Django knows to UPDATE vs. INSERT

明确指定自动主键值主要用于批量保存对象,当你确信不会发生主键碰撞时。

如果你使用的是 PostgreSQL,与主键相关的序列可能需要更新;参见 手动指定自增主键的值。

保存时会发生什么?

当你保存一个对象时,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(),而对象的主键属性 没有 定义一个 default 时,Django 会遵循这个算法。

  • 如果对象的主键属性被设置为值为 True (即,一个不是 None 或空字符串的值),Django 会执行 UPDATE
  • 如果对象的主键属性没有设置,或者 UPDATE 没有更新任何东西(例如主键被设置为数据库中不存在的值),Django 会执行 INSERT

如果对象的主键属性定义了一个 default,那么如果它是一个现有的模型实例,并且主键被设置为数据库中存在的值,Django 就会执行一个 UPDATE。否则,Django 会执行一个 INSERT

这里的一个问题是,如果你不能保证主键值未被使用,那么在保存新对象时,你应该注意不要显式地指定一个主键值。关于这个细微的差别,请看上面的 Explicitly specifying auto-primary-key values 和下面的 Forcing an INSERT or UPDATE

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

强制执行 INSERT 或 UPDATE

在一些罕见的情况下,有必要强制 save() 方法执行 SQL INSERT,而不是回到 UPDATE。或者反过来说:如果可能的话,更新,但不插入新的记录。在这些情况下,你可以将 force_insert=Trueforce_update=True 参数传递给 save() 方法。一起传递这两个参数是一个错误:你不能同时插入 更新!

你应该很少需要使用这些参数。Django 几乎总是会做正确的事情,试图覆盖会导致难以追踪的错误。这个功能只适合进阶使用。

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

基于现有字段更新属性

有时候你需要对字段执行简单的算术操作,比如增加或减少当前值。实现这一目标的一种方法是在 Python 中进行算术操作,比如:

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

如果从数据库中检索到的 number_sold 旧值是 10,那么 11 的值将被写回数据库。

这个过程可以通过 避免竞争条件 来使其更加健壮,并且稍微更快,因为它是相对于原始字段值的更新,而不是作为新值的显式赋值。Django 提供了 F 表达式 来执行这种相对更新。使用 F 表达式,前面的示例可以表达为:

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

更多细节,请参阅 F 表达式 及其 在更新查询中的使用 的文档。

指定要保存的字段

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

  1. product.name = "Name changed again"
  2. product.save(update_fields=["name"])

update_fields 参数可以是任何包含字符串的可迭代对象。一个空的 update_fields 可迭代对象将跳过保存。值为 None 将对所有字段进行更新。

指定 update_fields 将强制更新。

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

Field.pre_save()` 和 update_fields

如果传递了 update_fields,则只会调用 update_fieldspre_save() 方法。例如,这意味着具有 auto_now=True 的日期/时间字段将不会被更新,除非它们包含在 update_fields 中。

删除对象

Model.delete(using=DEFAULT_DB_ALIAS, keep_parents=False)

Model.adelete(using=DEFAULT_DB_ALIAS, keep_parents=False)

异步版本: adelete()

发出对象的 SQL DELETE。这只会删除数据库中的对象;Python 实例仍然存在,并且其字段中仍然包含数据,只是主键设置为 None。此方法返回删除的对象数量以及每种对象类型的删除数量字典。

更多细节,包括如何批量删除对象,请参见 删除对象

如果你想自定义删除行为,你可以覆盖 delete() 方法。更多细节请参见 重写之前定义的模型方法

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

Changed in Django 4.2:

已添加了 adelete() 方法。

Pickle 序列化对象

当你 pickle 一个模型时,它的当前状态被序列化。当你反序列化时,它将包含它被序列化时的模型实例,而不是当前数据库中的数据。

你不能在不同版本之间共享 pickle

模型的 pickle 只对生成它们的 Django 版本有效。如果你使用 Django 版本 N 生成了一个 pickle,那么不能保证这个 pickle 在 Django 版本 N+1 中可以被读取。Pickle 不应该作为长期存档策略的一部分。

由于 pickle 兼容性错误可能很难诊断,比如静默损坏对象,所以当你试图在 Django 版本中反序列化模型时,会发出 RuntimeWarning 的警告。

其他模型实例方法

有几个对象方法有特殊用途。

__str__()

Model.__str__()

每当你对一个对象调用 str() 时,就会调用 __str__() 方法。Django 在很多地方使用了 str(obj) 方法。最主要的是,在 Django 管理站点中显示一个对象,以及作为模板显示对象时插入的值。因此,你应该总是从 __str__() 方法中返回一个漂亮的、人类可读的模型表示。

例子:

  1. from django.db import models
  2. class Person(models.Model):
  3. first_name = models.CharField(max_length=50)
  4. last_name = models.CharField(max_length=50)
  5. def __str__(self):
  6. return f"{self.first_name} {self.last_name}"

__eq__()

Model.__eq__()

相等方法的定义是,具有相同主键值和相同具体类的实例被认为是相等的,但主键值为 None 的实例除自身外对任何事物都不相等。对于代理模型,具体类被定义为模型的第一个非代理父类;对于所有其他模型,它只是模型的类。

例子:

  1. from django.db import models
  2. class MyModel(models.Model):
  3. id = models.AutoField(primary_key=True)
  4. class MyProxyModel(MyModel):
  5. class Meta:
  6. proxy = True
  7. class MultitableInherited(MyModel):
  8. pass
  9. # Primary keys compared
  10. MyModel(id=1) == MyModel(id=1)
  11. MyModel(id=1) != MyModel(id=2)
  12. # Primary keys are None
  13. MyModel(id=None) != MyModel(id=None)
  14. # Same instance
  15. instance = MyModel(id=None)
  16. instance == instance
  17. # Proxy model
  18. MyModel(id=1) == MyProxyModel(id=1)
  19. # Multi-table inheritance
  20. 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 引用对象。

例子:

  1. def get_absolute_url(self):
  2. return "/people/%i/" % self.id

虽然这段代码正确且简单,但它可能不是写这种方法的最可移植的方式。reverse() 函数通常是最好的方法。

例子:

  1. def get_absolute_url(self):
  2. from django.urls import reverse
  3. return reverse("people-detail", kwargs={"pk": self.pk})

Django 使用 get_absolute_url() 的一个地方就是在管理应用中。如果一个对象定义了这个方法,那么对象编辑页面会有一个“View on site”的链接,直接跳转到对象的公开视图,就像 get_absolute_url() 给出的那样。

类似的,Django 的其他几个部分,比如 联合供稿框架,当定义了 get_absolute_url() 时,也会使用 get_absolute_url()。如果你的模型的每个实例都有一个唯一的 URL,你应该定义 get_absolute_url()

警告

你应该避免从未经验证的用户输入中建立 URL,以减少链接或重定向中毒的可能性:

  1. def get_absolute_url(self):
  2. return "/%s/" % self.name

如果 self.name'/example.com',这将返回 '//example.com/',这反过来又是一个有效的协议相对 URL,但不是预期的 '/%2Fexample.com/'

在模板中使用 get_absolute_url(),而不是硬编码你的对象的 URL,这是一个很好的做法。例如,这个模板代码就很糟糕:

  1. <!-- BAD template code. Avoid! -->
  2. <a href="/people/{{ object.id }}/">{{ object.name }}</a>

这个模板代码就好多了:

  1. <a href="{{ object.get_absolute_url }}">{{ object.name }}</a>

这里的逻辑是,如果你改变了你的对象的 URL 结构,即使是为了纠正拼写错误这样的小事,你也不想追踪 URL 可能被创建的每个地方。在 get_absolute_url() 中指定一次,然后让你的其他代码调用那个地方。

备注

get_absolute_url() 返回的字符串 必须 只包含 ASCII 字符(根据 URI 规范,RFC 3986#section-2),必要时需要进行 URL 编码。

调用 get_absolute_url() 的代码和模板应该可以直接使用结果,而不需要任何进一步的处理。如果你使用的字符串包含 ASCII 码范围以外的字符,你可能希望使用 django.utils.encoding.iri_to_uri() 函数来帮助解决这个问题。

额外的实例方法

除了 save()delete() 之外,一个模型对象还可能有以下一些方法:

Model.get_FOO_display()

对于每一个设置了 choice 的字段,该对象将有一个 get_FOO_display() 方法,其中 FOO 是字段的名称。该方法返回字段的“人类可读”值。

例子:

  1. from django.db import models
  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=2, choices=SHIRT_SIZES)
  1. >>> p = Person(name="Fred Flintstone", shirt_size="L")
  2. >>> p.save()
  3. >>> p.shirt_size
  4. 'L'
  5. >>> p.get_shirt_size_display()
  6. 'Large'

Model.get_next_by_FOO(**kwargs)

Model.get_previous_by_FOO(**kwargs)

对于每一个 DateFieldDateTimeField 没有 null=True,该对象将有 get_next_by_FOO()get_previous_by_FOO() 方法,其中 FOO 是字段名。这将返回与日期字段相关的下一个和上一个对象,适当时引发一个 DoesNotExist 异常。

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

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

覆盖额外的实例方法

在大多数情况下,覆盖或继承 get_FOO_display()get_next_by_FOO()get_previous_by_FOO() 应按预期工作。然而,由于它们是由元类添加的,所以要考虑所有可能的继承结构是不实际的。在更复杂的情况下,你应该覆盖 Field.contribution_to_class() 来设置你需要的方法。

其他属性

_state

Model._state

_state 属性指的是一个 ModelState 对象,它跟踪模型实例的生命周期。

ModelState 对象有两个属性。adding 是一个标志,如果模型尚未保存到数据库,则为 Truedb 是一个字符串,指的是实例从数据库加载或保存到的别名。

新实例有 adding=Truedb=None,因为它们尚未被保存。从 QuerySet 获取的实例将有 adding=Falsedb 设置为相关数据库的别名。