模型

模型是关于您的数据的唯一、确定的信息源。它包含要存储的数据的基本字段和行为。通常,每个模型映射到一个数据库表。

基础知识:

  • 每个模型都是一个python类,子类 django.db.models.Model .

  • 模型的每个属性表示一个数据库字段。

  • 通过所有这些,Django为您提供了一个自动生成的数据库访问API;请参见 进行查询 .

快速实例

此示例模型定义了 Person ,它有一个 first_namelast_name ::

from django.db import models


class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

first_namelast_namefields 模型。每个字段都被指定为类属性,并且每个属性映射到数据库列。

以上 Person 模型将创建这样的数据库表:

CREATE TABLE myapp_person (
    "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

一些技术说明:

  • 表的名称, myapp_person ,自动从某些模型元数据派生,但可以重写。见 表名 了解更多详细信息。

  • id 字段是自动添加的,但可以覆盖此行为。见 自动主键字段 .

  • 这个 CREATE TABLE 本例中的SQL是使用PostgreSQL语法格式化的,但值得注意的是,Django使用的SQL是根据您的 settings file .

使用模型

一旦你定义了你的模型,你需要告诉 Django 你将要 use 那些模型。通过编辑设置文件并更改 INSTALLED_APPS 用于添加包含您的 models.py .

例如,如果应用程序的模型位于模块中 myapp.models (由 manage.py startapp 脚本) INSTALLED_APPS 应阅读,部分内容:

INSTALLED_APPS = [
    # ...
    "myapp",
    # ...
]

当您将新应用程序添加到 INSTALLED_APPS ,一定要运行 manage.py migrate ,可以选择先使用 manage.py makemigrations .

字段

模型最重要的部分——也是模型唯一需要的部分——是它定义的数据库字段列表。字段由类属性指定。注意不要选择与 models API 喜欢 cleansavedelete .

例子::

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)


class Album(models.Model):
    artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

字段类型

模型中的每个字段都应该是适当的 Field 类。Django使用字段类类型来确定以下几点:

  • 列类型,它告诉数据库要存储的数据类型(例如 INTEGERVARCHARTEXT

  • 默认HTML widget 在呈现表单字段时使用(例如 <input type="text"><select>

  • 在Django的管理员和自动生成的表单中使用的最小验证要求。

Django提供了几十种内置字段类型;您可以在 model field reference . 如果Django的内置字段不起作用,您可以轻松地编写自己的字段;请参见 如何创建自定义模型字段 .

字段选项

每个字段采用一组特定于字段的参数(记录在 model field reference )例如, CharField (及其子类)要求 max_length 参数,指定 VARCHAR 用于存储数据的数据库字段。

还有一组公共参数可用于所有字段类型。所有都是可选的。它们在 reference ,但以下是最常用的摘要:

null

如果 True ,django将空值存储为 NULL 在数据库中。默认是 False .

blank

如果 True ,该字段允许为空。默认是 False .

注意这与 null . null 纯粹与数据库相关,而 blank 是否与验证相关。如果字段有 blank=True ,表单验证将允许输入空值。如果字段有 blank=False ,字段将是必需的。

choices

A sequence 两个元组中的一个用作此字段的选项。如果指定了此选项,则默认表单小部件将是一个选择框,而不是标准文本字段,并将选项限制为给定的选项。

选项列表如下所示:

YEAR_IN_SCHOOL_CHOICES = [
    ("FR", "Freshman"),
    ("SO", "Sophomore"),
    ("JR", "Junior"),
    ("SR", "Senior"),
    ("GR", "Graduate"),
]

备注

每次按 choices 变化。

每个元组中的第一个元素是将存储在数据库中的值。第二个元素由字段的表单小部件显示。

给定模型实例,字段的显示值 choices 可以使用 get_FOO_display() 方法。例如::

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'

也可以使用枚举类来定义 choices 简而言之:

from django.db import models


class Runner(models.Model):
    MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE")
    name = models.CharField(max_length=60)
    medal = models.CharField(blank=True, choices=MedalType, max_length=10)

更多示例可在 model field reference .

default

字段的默认值。这可以是一个值或可调用对象。如果可调用,则每次创建新对象时都会调用它。

help_text

要与表单小部件一起显示的额外“帮助”文本。它对文档很有用,即使表单上没有使用您的字段。

primary_key

如果 True ,此字段是模型的主键。

如果您不指定 primary_key=True 对于模型中的任何字段,Django将自动添加 IntegerField 保留主键,这样就不需要设置 primary_key=True 在任何字段上,除非要重写默认的主键行为。更多,请参见 自动主键字段 .

主键字段是只读的。如果更改现有对象的主键值,然后保存它,则将在旧对象旁边创建一个新对象。例如::

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)
<QuerySet ['Apple', 'Pear']>
unique

如果 True ,此字段在整个表中必须是唯一的。

同样,这些只是最常见字段选项的简短描述。详细信息可在 common model field option reference .

自动主键字段

默认情况下,Django为每个模型提供一个自动递增的主键,其类型为中每个应用程序指定的类型 AppConfig.default_auto_field 或全局地在 DEFAULT_AUTO_FIELD 设置。例如::

id = models.BigAutoField(primary_key=True)

如果要指定自定义主键,请指定 primary_key=True 在你的某个领域。如果Django看到你已经明确设置了 Field.primary_key ,它不会添加自动 id 列。

每个模型只需要一个字段 primary_key=True (显式声明或自动添加)。

详细字段名

每个字段类型,除了 ForeignKeyManyToManyFieldOneToOneField ,采用可选的第一位置参数——详细名称。如果没有给出详细名称,Django将使用字段的属性名自动创建该名称,并将下划线转换为空格。

在本例中,详细名称是 "person's first name" ::

first_name = models.CharField("person's first name", max_length=30)

在本例中,详细名称是 "first name" ::

first_name = models.CharField(max_length=30)

ForeignKeyManyToManyFieldOneToOneField 要求第一个参数为模型类,因此使用 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将在需要的地方自动大写第一个字母。

关系

显然,关系数据库的功能在于将表相互关联。Django提供了定义三种最常见的数据库关系类型的方法:多对一、多对多和一对一。

多对一关系

要定义多对一关系,请使用 django.db.models.ForeignKey . 你像其他人一样使用它 Field 类型:通过将其作为模型的类属性包含进来。

ForeignKey 需要位置参数:与模型相关的类。

例如,如果 Car 模型有 Manufacturer 也就是说,A Manufacturer 制造多辆车,但每辆 Car 只有一个 Manufacturer --使用以下定义:

from django.db import models


class Manufacturer(models.Model):
    # ...
    pass


class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    # ...

您还可以创建 recursive relationships (与自身有多对一关系的对象)和 relationships to models not yet definedthe model field reference 有关详细信息。

建议,但不要求 ForeignKey 领域 (manufacturer 在上面的例子中)是模型的名称,小写。你想叫什么都行。例如::

class Car(models.Model):
    company_that_makes_it = models.ForeignKey(
        Manufacturer,
        on_delete=models.CASCADE,
    )
    # ...

参见

ForeignKey 字段接受多个额外参数,这些参数在 the model field reference . 这些选项有助于定义关系的工作方式;所有选项都是可选的。

有关访问向后相关对象的详细信息,请参见 Following relationships backward example .

有关示例代码,请参见 Many-to-one relationship model example .

多对多关系

要定义多对多关系,请使用 ManyToManyField . 你像其他人一样使用它 Field 类型:通过将其作为模型的类属性包含进来。

ManyToManyField 需要位置参数:与模型相关的类。

例如,如果 Pizza 有多重 Topping 对象——也就是说, Topping 可以放在多个比萨饼上 Pizza 有多个浇头——下面是您如何表示的:

from django.db import models


class Topping(models.Model):
    # ...
    pass


class Pizza(models.Model):
    # ...
    toppings = models.ManyToManyField(Topping)

和一样 ForeignKey ,也可以创建 recursive relationships (与自身有多对多关系的对象)和 relationships to models not yet defined .

建议,但不要求 ManyToManyField (toppings 在上面的示例中)是描述相关模型对象集的复数。

哪种型号的 ManyToManyField 但是您应该只将它放在其中一个模型中——而不是同时放在这两个模型中。

一般来说, ManyToManyField 实例应该放在要在窗体上编辑的对象中。在上面的例子中, toppings 是在 Pizza (而不是 Topping 有一个 pizzas ManyToManyField )因为想一个比萨有配料比多个比萨有配料更自然。上面的设置方式, Pizza 表单允许用户选择浇头。

参见

Many-to-many relationship model example 举一个完整的例子。

ManyToManyField 字段还接受一些额外的参数,这些参数在 the model field reference . 这些选项有助于定义关系的工作方式;所有选项都是可选的。

多对多关系上的额外字段

当你只处理多对多的关系时,比如混合搭配比萨饼和配料,这是一个标准 ManyToManyField 是你所需要的。但是,有时您可能需要将数据与两个模型之间的关系相关联。

例如,考虑应用程序跟踪音乐家所属的音乐组的情况。一个人和他们所属的组之间存在多对多关系,因此您可以使用 ManyToManyField 代表这种关系。但是,关于您可能想要收集的成员身份有很多详细信息,例如此人加入该组的日期。

对于这些情况,Django允许您指定用于管理多对多关系的模型。然后可以在中间模型上放置额外的字段。中间模型与 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)

在设置中介模型时,可以显式地指定涉及多对多关系的模型的外键。此显式声明定义了两个模型的关联方式。

中间模型有一些限制:

  • 中间模型必须包含一个-和 only 源模型的一个外键(这将是 Group 在我们的示例中),或者必须显式指定关系应该使用的外键django ManyToManyField.through_fields . 如果您有多个外键并且 through_fields 未指定,将引发验证错误。类似的限制适用于目标模型的外键(这将是 Person 在我们的例子中)。

  • 对于通过中介模型与自身具有多对多关系的模型,允许同一模型的两个外键,但它们将被视为多对多关系的两个(不同)面。如果有 more 但是,必须指定两个以上的外键 through_fields 如上所述,否则将引发验证错误。

现在,您已经设置了 ManyToManyField 使用您的中介模型 (Membership ,在本例中),您已经准备好开始创建一些多对多关系。您可以通过创建中间模型的实例来执行此操作:

>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(
...     person=ringo,
...     group=beatles,
...     date_joined=date(1962, 8, 16),
...     invite_reason="Needed a new drummer.",
... )
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(
...     person=paul,
...     group=beatles,
...     date_joined=date(1960, 8, 1),
...     invite_reason="Wanted to form a band.",
... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

您还可以使用 add()create() ,或 set() 创建关系,只要您指定 through_defaults 对于任何必填字段:

>>> beatles.members.add(john, through_defaults={"date_joined": date(1960, 8, 1)})
>>> beatles.members.create(
...     name="George Harrison", through_defaults={"date_joined": date(1960, 8, 1)}
... )
>>> beatles.members.set(
...     [john, paul, ringo, george], through_defaults={"date_joined": date(1960, 8, 1)}
... )

您可能更喜欢直接创建中间模型的实例。

如果由中间模型定义的自定义直通表不强制 (model1, model2) 对,并允许多个值,则 remove() 调用将删除所有中间模型实例:

>>> Membership.objects.create(
...     person=ringo,
...     group=beatles,
...     date_joined=date(1968, 9, 4),
...     invite_reason="You've been gone for a month and we miss you.",
... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>

这个 clear() 方法可用于移除实例的所有多对多关系:

>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
<QuerySet []>

一旦建立了多对多关系,就可以发出查询。与正常的多对多关系一样,您可以使用多对多相关模型的属性进行查询:

# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith="Paul")
<QuerySet [<Group: The Beatles>]>

由于您使用的是中间模型,因此您还可以查询其属性:

# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
...     group__name="The Beatles", membership__date_joined__gt=date(1961, 1, 1)
... )
<QuerySet [<Person: Ringo Starr]>

如果您需要访问成员的信息,您可以通过直接查询 Membership 型号:

>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

访问相同信息的另一种方式是通过查询 many-to-many reverse relationship 从一个 Person 对象:

>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

一对一关系

要定义一对一关系,请使用 OneToOneField . 你像其他人一样使用它 Field 类型:通过将其作为模型的类属性包含进来。

当对象以某种方式“扩展”另一个对象时,对于该对象的主键来说,这是最有用的。

OneToOneField 需要位置参数:与模型相关的类。

例如,如果您正在构建一个“地点”数据库,那么您将在数据库中构建相当标准的内容,例如地址、电话号码等。然后,如果你想在Restaurant的顶部建立一个数据库,而不是重复你自己并在 Restaurant 模型,你可以做 Restaurant 有一个 OneToOneFieldPlace (因为Restaurant“是”的地方;事实上,要处理这个问题,你通常会使用 inheritance 其中包含一对一的隐式关系)。

和一样 ForeignKey ,A recursive relationship 可以定义和 references to as-yet undefined models 可以制作。

参见

One-to-one relationship model example 举一个完整的例子。

OneToOneField 字段还接受可选 parent_link 参数。

OneToOneField 用于自动成为模型上的主键的类。这不再是正确的(尽管您可以手动传递 primary_key 如果你愿意的话。因此,现在可以有多个类型的字段 OneToOneField 在一个模型上。

跨文件模型

将一个模型与另一个应用程序中的模型关联起来是完全可以的。为此,请在定义模型的文件顶部导入相关模型。然后,在需要的地方参考其他模型类。例如::

from django.db import models
from geography.models import ZipCode


class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(
        ZipCode,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

字段名限制

Django对模型字段名进行了一些限制:

  1. 字段名不能是Python保留字,因为这会导致Python语法错误。例如:

    class Example(models.Model):
        pass = models.IntegerField() # 'pass' is a reserved word!
    
  2. 由于django的查询查找语法的工作方式,字段名在一行中不能包含多个下划线。例如::

    class Example(models.Model):
        foo__bar = models.IntegerField()  # 'foo__bar' has two underscores!
    
  3. 字段名不能以下划线结尾,原因类似。

但是,可以绕过这些限制,因为字段名不必与数据库列名匹配。见 db_column 选择权。

SQL保留字,例如 joinwhereselectare 允许作为模型字段名,因为django会在每个基础SQL查询中转义所有数据库表名和列名。它使用特定数据库引擎的引用语法。

自定义字段类型

如果一个现有的模型字段不能用于您的目的,或者您希望利用一些不太常见的数据库列类型,则可以创建自己的字段类。在中提供了创建自己字段的全部覆盖范围。 如何创建自定义模型字段 .

Meta 选项

通过使用内部 class Meta ,像这样::

from django.db import models


class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

模型元数据是“任何非字段的内容”,例如排序选项 (ordering )数据库表名 (db_table )或人类可读的单数和复数名称 (verbose_nameverbose_name_plural )不需要,并添加 class Meta 对于模型是完全可选的。

所有可能的完整清单 Meta 选项可以在 model option reference .

模型属性

objects

模型最重要的属性是 Manager . 它是向django模型提供数据库查询操作的接口,用于 retrieve the instances 从数据库中。如果没有习惯 Manager 已定义,默认名称为 objects . 管理器只能通过模型类访问,而不能通过模型实例访问。

模型方法

在模型上定义自定义方法,以便向对象添加自定义的“行级别”功能。反之 Manager 方法是用来做“表范围”的事情,模型方法应该作用于特定的模型实例。

这是一种将业务逻辑保存在一个地方的有价值的技术——模型。

例如,此模型有几个自定义方法:

from django.db import models


class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime

        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    @property
    def full_name(self):
        "Returns the person's full name."
        return f"{self.first_name} {self.last_name}"

本例中的最后一个方法是 property .

这个 model instance reference 有一个完整的列表 methods automatically given to each model . 您可以覆盖其中的大部分--请参见 overriding predefined model methods ,下面--但您几乎总是希望定义以下几项:

__str__()

返回任何对象的字符串表示形式的python“magic method”。每当需要强制模型实例并将其显示为纯字符串时,python和django都将使用这种方法。最值得注意的是,当您在交互式控制台或管理员中显示对象时,就会发生这种情况。

您将始终希望定义此方法;默认值一点也不太有用。

get_absolute_url()

这将告诉Django如何计算对象的URL。Django在其管理界面中使用它,并且在任何时候它都需要为一个对象找到一个URL。

任何具有唯一标识该对象的URL的对象都应定义此方法。

重写预定义的模型方法

还有一套 model methods 它封装了一系列您想要定制的数据库行为。尤其是你经常想改变的方式 save()delete() 工作。

您可以自由地重写这些方法(以及任何其他模型方法)来更改行为。

覆盖内置方法的一个经典用例是,如果您希望在每次保存对象时都发生一些事情。例如(参见 save() 对于它接受的参数的文档)::

from django.db import models


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs)  # Call the "real" save() method.
        do_something_else()

您还可以阻止保存:

from django.db import models


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        if self.name == "Yoko Ono's blog":
            return  # Yoko shall never have her own blog!
        else:
            super().save(*args, **kwargs)  # Call the "real" save() method.

记住调用超类方法是很重要的——就是这样 super().save(*args, **kwargs) 业务——确保对象仍然保存到数据库中。如果忘记调用超类方法,则不会发生默认行为,也不会触及数据库。

传递可以传递给模型方法的参数也是很重要的——这就是 *args, **kwargs 比特可以。Django将不时地扩展内置模型方法的功能,添加新的参数。如果你使用 *args, **kwargs 在方法定义中,可以保证在添加参数时,代码将自动支持这些参数。

如果您希望更新 save() 方法,您可能还希望将此字段添加到 update_fields 关键字参数。这将确保在以下情况下保存字段 update_fields 是指定的。例如::

from django.db import models
from django.utils.text import slugify


class Blog(models.Model):
    name = models.CharField(max_length=100)
    slug = models.TextField()

    def save(
        self, force_insert=False, force_update=False, using=None, update_fields=None
    ):
        self.slug = slugify(self.name)
        if update_fields is not None and "name" in update_fields:
            update_fields = {"slug"}.union(update_fields)
        super().save(
            force_insert=force_insert,
            force_update=force_update,
            using=using,
            update_fields=update_fields,
        )

看见 指定要保存的字段 了解更多详细信息。

未对批量操作调用重写的模型方法

请注意 delete()deleting objects in bulk using a QuerySet 或者由于 cascading delete . 为了确保执行自定义的删除逻辑,可以使用 pre_delete 和/或 post_delete 信号。

不幸的是,当 creatingupdating 大量对象,因为没有 save()pre_savepost_save 被称为。

正在执行自定义SQL

另一种常见的模式是在模型方法和模块级方法中编写自定义SQL语句。有关使用原始SQL的详细信息,请参阅 using raw SQL .

模型继承

Django中的模型继承的工作方式与Python中的正常类继承的工作方式几乎相同,但仍应遵循页面开头的基础知识。这意味着基类应该子类 django.db.models.Model .

您需要做的唯一决定是,您是否希望父模型本身就是模型(具有自己的数据库表),或者父模型只是公共信息的持有者,这些公共信息只能通过子模型可见。

Django有三种继承方式。

  1. 通常,您只需要使用父类来保存不需要为每个子模型键入的信息。这个类不会单独使用,所以 抽象基类 就是你想要的。

  2. 如果您要对现有模型进行子类化(可能完全来自另一个应用程序),并希望每个模型都有自己的数据库表, 多表继承 是前进的道路。

  3. 最后,如果只想修改模型的Python级行为,而不想以任何方式更改模型字段,那么可以使用 代理模型 .

抽象基类

当您希望将一些公共信息放入许多其他模型中时,抽象基类非常有用。你写你的基础课 abstract=TrueMeta 类。然后,该模型将不会用于创建任何数据库表。相反,当它用作其他模型的基类时,它的字段将添加到子类的字段中。

一个例子:

from django.db import models


class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True


class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

这个 Student 模型将有三个字段: nameagehome_group . 这个 CommonInfo 模型不能用作普通的Django模型,因为它是抽象基类。它不生成数据库表或没有管理器,不能直接实例化或保存。

从抽象基类继承的字段可以用另一个字段或值重写,也可以用 None .

对于许多用途,这种类型的模型继承将完全满足您的需要。它提供了一种在Python级别分解公共信息的方法,同时在数据库级别为每个子模型只创建一个数据库表。

Meta 遗传

创建抽象基类时,Django将 Meta 在基类中声明的内部类可用作属性。如果子类不声明自己的类 Meta 类,它将继承父级的 Meta . 如果孩子想延长父母的 Meta 类,它可以将其子类化。例如::

from django.db import models


class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ["name"]


class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        db_table = "student_info"

Django确实对 Meta 抽象基类的类:在安装 Meta 属性,它设置 abstract=False . 这意味着抽象基类的子类本身不会自动成为抽象类。要生成继承自另一个抽象基类的抽象基类,需要显式地设置 abstract=True 在孩子身上。

某些属性不能包含在 Meta 抽象基类的类。例如,包括 db_table 意味着所有的子类(那些没有指定自己的类) Meta )将使用相同的数据库表,这几乎肯定不是您想要的。

由于Python继承的工作方式,如果一个子类从多个抽象基类继承,则只有 Meta 默认情况下,将继承第一个列出的类中的选项。继承 Meta 来自多个抽象基类的选项,必须显式声明 Meta 继承权。例如::

from django.db import models


class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True
        ordering = ["name"]


class Unmanaged(models.Model):
    class Meta:
        abstract = True
        managed = False


class Student(CommonInfo, Unmanaged):
    home_group = models.CharField(max_length=5)

    class Meta(CommonInfo.Meta, Unmanaged.Meta):
        pass

多表继承

Django支持的第二种类型的模型继承是当层次结构中的每个模型都是一个单独的模型时。每个模型都对应于自己的数据库表,可以单独查询和创建。继承关系引入子模型与其每个父模型之间的链接(通过自动创建的 OneToOneField )例如::

from django.db import models


class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)


class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

的所有字段 Place 也将在以下时间提供 Restaurant ,尽管数据将驻留在不同的数据库表中。所以这两种情况都是可能的:

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

如果你有一个 Place 这也是一个 Restaurant ,您可以从 Place 对象添加到 Restaurant 通过使用模型名称的小写版本来创建:

>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>

然而,如果 p 在上面的例子中 notRestaurant (它被直接创建为 Place 对象或是其他类的父级),指 p.restaurant 会提高 Restaurant.DoesNotExist 例外。

自动创建的 OneToOneFieldRestaurant 链接到 Place 如下所示:

place_ptr = models.OneToOneField(
    Place,
    on_delete=models.CASCADE,
    parent_link=True,
    primary_key=True,
)

您可以通过声明自己的 OneToOneField 具有 parent_link=TrueRestaurant .

Meta 多表继承

在多表继承情况下,子类从其父类继承是没有意义的。 Meta 类。所有的 Meta 选项已经应用到父类,再次应用它们通常只会导致矛盾的行为(这与抽象的基类情况相反,在抽象的基类情况下,基类本身并不存在)。

因此子模型不能访问其父模型 Meta 类。但是,在一些有限的情况下,子级继承父级的行为:如果子级没有指定 ordering 属性或A get_latest_by 属性,它将从其父级继承这些属性。

如果父级具有排序,并且您不希望子级具有任何自然排序,则可以显式禁用它:

class ChildModel(ParentModel):
    # ...
    class Meta:
        # Remove parent's ordering effect
        ordering = []

继承与逆向关系

因为多表继承使用隐式 OneToOneField 要链接子级和父级,可以从父级向下移动到子级,如上面的示例所示。但是,这会使用默认名称 related_name 价值观 ForeignKeyManyToManyField 关系。如果要将这些类型的关系放到父模型的子类上,则 must 指定 related_name 属性。如果忘记了,Django将引发验证错误。

例如,使用上面的 Place 再次初始化,让我们用 ManyToManyField ::

class Supplier(Place):
    customers = models.ManyToManyField(Place)

这会导致错误:

Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.

HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.

添加 related_namecustomers 以下字段将解决错误: models.ManyToManyField(Place, related_name='provider') .

代理模型

使用时 multi-table inheritance 为模型的每个子类创建一个新的数据库表。这通常是所需的行为,因为子类需要一个位置来存储基类上不存在的任何其他数据字段。但是,有时您只想更改模型的python行为——可能是更改默认管理器,或者添加一个新方法。

这就是代理模型继承的目的:创建 代理 对于原始模型。您可以创建、删除和更新代理模型的实例,所有数据都将像使用原始(非代理)模型一样保存。区别在于,您可以更改默认模型排序或代理中的默认管理器等内容,而不必更改原始模型。

代理模型与普通模型一样声明。通过设置 proxy 的属性 Meta 类到 True .

例如,假设您要将一个方法添加到 Person 模型。你可以这样做:

from django.db import models


class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)


class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

这个 MyPerson 类在与其父级相同的数据库表上操作 Person 班级。特别是,任何新的 Person 也可以通过以下方式访问 MyPerson ,反之亦然:

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

您还可以使用代理模型来定义模型上不同的默认顺序。您可能并不总是想订购 Person 模型,但定期按 last_name 使用代理时的属性::

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

现在正常 Person 查询将无序,并且 OrderedPerson 查询将按 last_name .

代理模型继承 Meta 属性 in the same way as regular models .

QuerySet S仍然返回请求的模型

没有办法让 Django 回来,比如说 MyPerson 无论何时查询 Person 对象。一个查询集 Person 对象将返回这些类型的对象。代理对象的关键是依赖原始代码的代码 Person 将使用这些代码,并且您自己的代码可以使用所包含的扩展(其他代码都不依赖)。它不是替代 Person (或任何其他)用你自己创造的东西做模型。

基类限制

代理模型只能从一个非抽象模型类继承。不能从多个非抽象模型继承,因为代理模型不提供不同数据库表中的行之间的任何连接。代理模型可以从任意数量的抽象模型类继承,前提是它们可以继承 not 定义任何模型字段。代理模型还可以从共享公共非抽象父类的任意数量的代理模型继承。

代理模型管理器

如果不在代理模型上指定任何模型管理器,它将从其模型父代继承这些管理器。如果您在代理模型上定义了一个管理器,它将成为默认的,尽管在父类上定义的任何管理器仍然可用。

继续上面的示例,可以更改查询 Person 模型如下:

from django.db import models


class NewManager(models.Manager):
    # ...
    pass


class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

如果要向代理添加新的管理器,而不替换现有的默认值,则可以使用 custom manager 文档:创建一个包含新管理器的基类,并在主基类之后继承它:

# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
    secondary = NewManager()

    class Meta:
        abstract = True


class MyPerson(Person, ExtraManagers):
    class Meta:
        proxy = True

你可能不需要经常这样做,但是,当你这样做的时候,这是可能的。

代理继承和非托管模型之间的区别

代理模型继承看起来可能与使用 managed 模型的属性 Meta 类。

小心设置 Meta.db_table 您可以创建一个非托管模型,该模型隐藏现有模型并向其中添加python方法。然而,这将是非常重复和脆弱的,因为您需要保持两个副本同步,如果您做任何更改。

另一方面,代理模型的行为与它们代理的模型完全相同。它们总是与父模型同步,因为它们直接继承其字段和管理器。

一般规则如下:

  1. 如果要镜像现有模型或数据库表,但不需要所有原始数据库表列,请使用 Meta.managed=False . 该选项通常用于建模不在Django控制下的数据库视图和表。

  2. 如果您希望更改模型的纯Python行为,但要保留与原始字段相同的所有字段,请使用 Meta.proxy=True . 这将进行设置,以便在保存数据时,代理模型是原始模型存储结构的精确副本。

多重继承

正如Python的子类化一样,django模型也可能从多个父模型继承。请记住,标准的Python名称解析规则适用。一个特定名称(例如 Meta )出现在中的将是所使用的;例如,这意味着如果多个父级包含 Meta 类,只有第一个将被使用,其他所有将被忽略。

一般来说,您不需要从多个父级继承。这对“混入”类很有用的主要用例是:向继承混入的每个类添加一个特定的额外字段或方法。尽量保持继承层次结构的简单和简单,这样您就不必费劲去找出特定信息的来源。

请注意,继承自具有公共 id 主键字段将引发错误。要正确使用多重继承,可以使用显式 AutoField 在基本模型中:

class Article(models.Model):
    article_id = models.AutoField(primary_key=True)
    ...


class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    ...


class BookReview(Book, Article):
    pass

或者用一个共同的祖先持有 AutoField . 这需要使用显式 OneToOneField 从每个父模型到公共祖先,以避免子模型自动生成和继承的字段之间发生冲突:

class Piece(models.Model):
    pass


class Article(Piece):
    article_piece = models.OneToOneField(
        Piece, on_delete=models.CASCADE, parent_link=True
    )
    ...


class Book(Piece):
    book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
    ...


class BookReview(Book, Article):
    pass

不允许字段名“隐藏”

在普通的Python类继承中,允许子类重写父类中的任何属性。在Django中,这通常不允许用于模型字段。如果非抽象模型基类有一个名为 author ,不能创建其他模型字段或定义名为 author 在继承该基类的任何类中。

此限制不适用于从抽象模型继承的模型字段。这些字段可以被另一个字段或值覆盖,也可以通过设置 field_name = None .

警告

模型管理器是从抽象基类继承的。重写由继承的引用的继承字段 Manager 可能导致细微的错误。见 custom managers and model inheritance .

备注

一些字段定义模型上的额外属性,例如 ForeignKey 使用定义额外属性 _id 附加到字段名,以及 related_namerelated_query_name 关于外国模式。

这些额外属性不能被重写,除非定义它的字段被更改或删除,以便它不再定义额外属性。

覆盖父模型中的字段会在初始化新实例(指定在中初始化哪个字段)等方面造成困难 Model.__init__ )和序列化。这些是正常的Python类继承不需要以完全相同的方式处理的特性,因此django模型继承和Python类继承之间的区别不是任意的。

此限制仅适用于以下属性: Field 实例。如果愿意,可以重写普通的python属性。它也只适用于python看到的属性名:如果手动指定数据库列名,则可以在多表继承的子模型和祖先模型中显示相同的列名(它们是两个不同数据库表中的列)。

Django将提高 FieldError 如果覆盖任何祖先模型中的任何模型字段。

请注意,由于在类定义期间解析字段的方式,从多个抽象父模型继承的模型字段严格按照深度优先的顺序进行解析。这与标准的Python MRO不同,后者在菱形继承的情况下以广度优先的方式进行解析。这种差异只影响复杂的模型层次结构,根据上面的建议,您应该尽量避免这种情况。

在包中组织模型

这个 manage.py startapp 命令创建包含 models.py 文件。如果您有许多模型,将它们组织在单独的文件中可能很有用。

为此,请创建 models 包。去除 models.py 创建一个 myapp/models/ 目录 __init__.py 文件和用于存储模型的文件。必须将模型导入到 __init__.py 文件。

例如,如果你有 organic.pysynthetic.pymodels 目录:

myapp/models/__init__.py
from .organic import Person
from .synthetic import Robot

显式导入每个模型,而不是使用 from .models import * 它的优点是不使名称空间混乱,使代码更具可读性,并使代码分析工具保持有用。

参见

The Models Reference

涵盖所有与模型相关的API,包括模型字段、相关对象和 QuerySet .