模型实例参考

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

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

创建对象

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

class Model(**kwargs)

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

备注

You may be tempted to customize the model by overriding the __init__ method. If you do so, however, take care not to change the calling signature as any change may prevent the model instance from being saved. Additionally, referring to model fields within __init__ may potentially result in infinite recursion errors in some circumstances. Rather than overriding __init__, try using one of these approaches:

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

从数据库中刷新对象

If you delete a field from a model instance, accessing it again reloads the value from the database:

  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)

Asynchronous version: 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() method was added.

验证对象

There are four steps involved in validating a model:

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

All four steps are performed when you call a model’s full_clean() method.

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

警告

Constraints containing JSONField may not raise validation errors as key, index, and path transforms have many database-specific caveats. This may be fully supported later.

You should always check that there are no log messages, in the django.db.models logger, like “Got a database error calling check() on …” to confirm it’s validated properly.

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

This method calls Model.clean_fields(), Model.clean(), Model.validate_unique() (if validate_unique is True), and Model.validate_constraints() (if validate_constraints is True) in that order and raises a ValidationError that has a message_dict attribute containing errors from all four stages.

The optional exclude argument can be used to provide a set of field names that can be excluded from validation and cleaning. ModelForm uses this argument to exclude fields that aren’t present on your form from being validated since any errors raised could not be corrected by the user.

请注意,当您调用模型的 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() 执行的第一步是清理每个单独的字段。

Model.clean_fields(exclude=None)

This method will validate all fields on your model. The optional exclude argument lets you provide a set of field names to exclude from validation. It will raise a ValidationError if any fields fail validation.

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. )

Then, full_clean() will check unique constraints on your model.

如果字段没有出现在 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)

This method is similar to clean_fields(), but validates uniqueness constraints defined via Field.unique, Field.unique_for_date, Field.unique_for_month, Field.unique_for_year, or Meta.unique_together on your model instead of individual field values. The optional exclude argument allows you to provide a set of field names to exclude from validation. It will raise a ValidationError if any fields fail validation.

UniqueConstraints defined in the Meta.constraints are validated by Model.validate_constraints().

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

Finally, full_clean() will check any other constraints on your model.

Model.validate_constraints(exclude=None)

This method validates all constraints defined in Meta.constraints. The optional exclude argument allows you to provide a set of field names to exclude from validation. It will raise a ValidationError if any constraints fail validation.

保存对象

要将对象保存回数据库,调用 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)

Asynchronous version: asave()

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

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

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

Changed in Django 4.2:

asave() method was added.

自增主键

If a model has an AutoField — an auto-incrementing primary key — then that auto-incremented value will be calculated and saved as an attribute on your object the first time you call 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 的属性。它的行为就像模型上的一个普通属性,但实际上是模型主键字段属性的别名。您可以像读取和设置任何其他属性一样读取和设置这个值,它将更新模型中的正确字段。

明确指定自动主键值

If a model has an AutoField but you want to define a new object’s ID explicitly when saving, define it explicitly before saving, rather than relying on the auto-assignment of the 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 的?

You may have noticed Django database objects use the same save() method for creating and changing objects. Django abstracts the need to use INSERT or UPDATE SQL statements. Specifically, when you call save() and the object’s primary key attribute does not define a default or db_default, Django follows this algorithm:

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

If the object’s primary key attribute defines a default or db_default then Django executes an UPDATE if it is an existing model instance and primary key is set to a value that exists in the database. Otherwise, Django executes an 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 来恢复到旧算法。

Changed in Django 5.0:

The Field.db_default parameter was added.

强制执行 INSERT 或 UPDATE

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

When using multi-table inheritance, it’s also possible to provide a tuple of parent classes to force_insert in order to force INSERT statements for each base. For example:

  1. Restaurant(pk=1, name="Bob's Cafe").save(force_insert=(Place,))
  2. Restaurant(pk=1, name="Bob's Cafe", rating=4).save(force_insert=(Place, Rating))

You can pass force_insert=(models.Model,) to force an INSERT statement for all parents. By default, force_insert=True only forces the insertion of a new row for the current model.

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

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

Changed in Django 5.0:

Support for passing a tuple of parent classes to force_insert was added.

基于现有字段更新属性

Sometimes you’ll need to perform a simple arithmetic task on a field, such as incrementing or decrementing the current value. One way of achieving this is doing the arithmetic in Python like:

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

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

The process can be made robust, avoiding a race condition, as well as slightly faster by expressing the update relative to the original field value, rather than as an explicit assignment of a new value. Django provides F expressions for performing this kind of relative update. Using F expressions, the previous example is expressed as:

  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

If update_fields is passed in, only the pre_save() methods of the update_fields are called. For example, this means that date/time fields with auto_now=True will not be updated unless they are included in the update_fields.

删除对象

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

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

Asynchronous version: adelete()

Issues an SQL DELETE for the object. This only deletes the object in the database; the Python instance will still exist and will still have data in its fields, except for the primary key set to None. This method returns the number of objects deleted and a dictionary with the number of deletions per object type.

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

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

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

Changed in Django 4.2:

adelete() method was added.

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() 中指定一次,然后让你的其他代码调用那个地方。

备注

The string you return from get_absolute_url() must contain only ASCII characters (required by the URI specification, RFC 3986#section-2) and be URL-encoded, if necessary.

调用 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 设置为相关数据库的别名。