Skip to main content

楷模

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

基础:

  • 每个模型都是一个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_name 是模型的 fields。每个字段都被指定为类属性,每个属性映射到数据库列。

上面的 Person 模型将创建一个这样的数据库表:

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

一些技术说明:

  • 表的名称 myapp_person 从一些模型元数据自动派生,但可以覆盖。有关详细信息,请参阅 表名

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

  • 本示例中的 CREATE TABLE SQL使用PostgreSQL语法格式化,但值得注意的是,Django使用针对您的 设置文件 中指定的数据库后端定制的SQL。

使用模型

一旦你定义了你的模型,你需要告诉Django你将去 use 那些模型。通过编辑设置文件并更改 INSTALLED_APPS 设置以添加包含 models.py 的模块的名称,来执行此操作。

例如,如果您的应用程序的模型存在于模块 myapp.models (由 manage.py startapp 脚本为应用程序创建的包结构)中,则 INSTALLED_APPS 应阅读部分:

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

当您向 INSTALLED_APPS 添加新应用程序时,请确保运行 manage.py migrate,可选择首先使用 manage.py makemigrations 进行迁移。

字段

模型的最重要的部分 - 模型的唯一必需部分 - 是它定义的数据库字段的列表。字段由类属性指定。注意不要选择与 模型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)。

  • 在呈现表单字段(例如 <input type="text"><select>)时使用的默认HTML 窗口小部件

  • 最小验证要求,用于Django admin及其自动生成的表单。

Django附带了几十个内置字段类型;你可以在 模型场参考 中找到完整的列表。你可以很容易地编写自己的字段,如果Django的内置的不做的伎俩;见 编写自定义模型字段

字段选项

每个字段都需要一组特定字段的参数(在 模型场参考 中记录)。例如,CharField (及其子类)需要一个 max_length 参数,它指定用于存储数据的 VARCHAR 数据库字段的大小。

还有一组可用于所有字段类型的常见参数。所有都是可选的。他们在 参考 中完全解释,但这里是最常用的一个快速摘要:

null

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

blank

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

请注意,这不同于 nullnull 纯粹是数据库相关的,而 blank 是与验证相关的。如果字段具有 blank=True,则表单验证将允许输入空值。如果字段具有 blank=False,则该字段将是必需的。

choices

可用作该字段选择的2元组的可迭代(例如,列表或元组)。如果给定,默认表单窗口小部件将是一个选择框,而不是标准文本字段,并将限制选择给出的选择。

选项列表如下所示:

YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
)

每个元组中的第一个元素是将存储在数据库中的值。第二个元素将由默认表单窗口小部件或在 ModelChoiceField 中显示。给定模型实例,可以使用 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'
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)
['Apple', 'Pear']
unique

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

同样,这些只是最常见的字段选项的简短描述。完整的详细信息可以在 公共模型字段选项引用 中找到。

自动主键字段

默认情况下,Django为每个模型提供以下字段:

id = models.AutoField(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提供了定义三种最常见类型的数据库关系的方法:many-to-one,many-to-many和one-to-one。

多对一的关系

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

ForeignKey 需要一个位置参数:模型所关联的类。

例如,如果 Car 模型具有 Manufacturer - 即 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)
    # ...

您还可以创建 递归关系 (与自身具有多对一关系的对象)和 与尚未定义的模型的关系;有关详细信息,请参阅 模型字段引用

建议(但不是必须),ForeignKey 字段的名称(上例中的 manufacturer)是模型的名称,小写。当然,你可以调用任何你想要的字段。例如:

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

参见

ForeignKey 字段接受 模型字段引用 中解释的一些额外的参数。这些选项有助于定义关系应如何工作;所有都是可选的。

有关访问与后端相关的对象的详细信息,请参阅 以下关系为例

有关示例代码,请参阅 多对一关系模型示例

多对多关系

要定义多对多关系,请使用 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 一样,您也可以创建 递归关系 (与自身具有多对多关系的对象)和 与尚未定义的模型的关系

建议(但不是必须的) ManyToManyField 的名称(上例中的 toppings)是描述相关模型对象集合的复数。

不管哪个模型有 ManyToManyField,但你只应该把它放在一个模型中 - 而不是两个。

一般来说,ManyToManyField 实例应该放在将要在表单上编辑的对象中。在上面的例子中,toppings 是在 Pizza (而不是 Toppingpizzas ManyToManyField),因为考虑一个披萨比顶部比多个比萨饼更自然。上面设置的方式,Pizza 形式将让用户选择浇头。

参见

有关完整示例,请参阅 多对多关系模型示例

ManyToManyField 字段也接受在 模型字段引用 中解释的一些额外的参数。这些选项有助于定义关系应如何工作;所有都是可选的。

多对多关系上的额外字段

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

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

对于这些情况,Django允许您指定将用于管理多对多关系的模型。然后,您可以在中间模型上放置额外的字段。中间模型与 ManyToManyField 相关联,使用 through 参数指向将充当中介的模型。对于我们的音乐家例子,代码看起来像这样:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __str__(self):              # __unicode__ on Python 2
        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,否则将产生验证错误。

  • 当使用中间模型定义从模型到自身的多对多关系时,must 使用 symmetrical=False (请参阅 模型字段引用)。

现在你已经设置了 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>]>

与正常的多对多字段不同,can’t 使用 add()create()set() 来创建关系:

>>> # THIS WILL NOT WORK
>>> beatles.members.add(john)
>>> # NEITHER WILL THIS
>>> beatles.members.create(name="George Harrison")
>>> # AND NEITHER WILL THIS
>>> beatles.members.set([john, paul, ringo, george])

为什么?您不能仅在 PersonGroup 之间创建关系 - 您需要指定 Membership 模型所需的关系的所有详细信息。简单的 addcreate 和赋值调用不提供一种方法来指定这个额外的细节。因此,对于使用中间模型的多对多关系,它们被禁用。创建此类型关系的唯一方法是创建中间模型的实例。

由于类似的原因,remove() 方法被禁用。例如,如果由中间模型定义的自定义通过表不强制 (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 WILL NOT WORK BECAUSE IT CANNOT TELL WHICH MEMBERSHIP TO REMOVE
>>> beatles.members.remove(ringo)

但是,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.'

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

>>> 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 有一个 OneToOneFieldPlace (因为餐厅“是一个地方;事实,处理这个你通常使用 遗产,这涉及一个隐含的一对一的关系)。

ForeignKey 一样,可以定义 递归关系 并且可以制作 对未定义模型的引用

参见

有关完整示例,请参阅 一对一关系模型示例

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!
    

这些限制可以解决,但是,因为您的字段名称不一定必须匹配您的数据库列名称。请参阅 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 选项的完整列表可以在 模型选项引用 中找到。

模型属性

objects

模型最重要的属性是 Manager。它是通过其向Django模型提供数据库查询操作的接口,用于从数据库获取 检索实例。如果未定义自定义 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"

    def _get_full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)
    full_name = property(_get_full_name)

此示例中的最后一个方法是 property

模型实例引用 有完整的 方法自动给予每个模型 清单。你可以覆盖大部分这些 - 见下面的 overriding predefined model methods,但有几个你几乎总是想定义:

__str__() (Python 3)

一个Python“魔法方法”,返回任何对象的unicode“表示”。这是Python和Django在模型实例需要强制并显示为纯字符串时使用的方法。最明显的是,当在交互式控制台或管理员中显示对象时,会发生这种情况。

你总是想定义这个方法;默认情况下根本不是很有帮助。

__unicode__() (Python 2)

Python 2相当于 __str__()

get_absolute_url()

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

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

覆盖预定义的模型方法

还有另一组 模型方法 封装了一堆你想要自定义的数据库行为。特别是你经常想改变 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(Blog, self).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(Blog, self).save(*args, **kwargs) # Call the "real" save() method.

重要的是要记住调用超类方法 - 那就是 super(Blog, self).save(*args, **kwargs) 业务 - 以确保对象仍然保存到数据库中。如果你忘记调用超类方法,默认行为不会发生,数据库不会被触摸。

同样重要的是你通过可以传递给模型方法的参数 - 这就是 *args, **kwargs 位。 Django将不时地扩展内置模型方法的能力,添加新的参数。如果您在方法定义中使用 *args, **kwargs,那么您将确保您的代码在添加时将自动支持这些参数。

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

注意,用于对象的 delete() 方法不一定在 使用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=True 放在 类中。此模型将不会用于创建任何数据库表。相反,当它被用作其他模型的基类时,它的字段将被添加到子类的那些。在抽象基类中的字段与子类中的字段具有相同的名称(和Django将引发异常)是错误的。

一个例子:

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_groupCommonInfo 模型不能用作正常的Django模型,因为它是一个抽象基类。它不生成数据库表或具有管理器,并且不能直接实例化或保存。

对于许多用途,这种类型的模型继承将是你想要的。它提供了一种在Python级别解决常见信息的方法,同时仍然只在数据库级别为每个子模型创建一个数据库表。

Meta 继承

当创建一个抽象基类时,Django使得您在基类中声明的任何 内部类可用作属性。如果子类没有声明自己的 类,它将继承父类的 。如果孩子想要扩展父类的 类,它可以子类化它。例如:

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对抽象基类的 类做了一个调整:在安装 属性之前,它设置 abstract=False。这意味着抽象基类的孩子不会自动成为抽象类本身。当然,你可以创建一个继承自另一个抽象基类的抽象基类。你只需要记住每次显式地设置 abstract=True

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

多表继承

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

如果您有一个也是 RestaurantPlace,您可以使用模型名称的小写版本从 Place 对象到 Restaurant 对象:

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

然而,如果上述示例中的 pnot a Restaurant (它已经直接创建为 Place 对象或者是其他类的父类),则引用 p.restaurant 将引发 Restaurant.DoesNotExist 异常。

Meta 和多表继承

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

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

如果父级有一个排序,并且您不希望子级有任何自然排序,您可以明确禁用它:

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

继承和反向关系

因为多表继承使用隐式 OneToOneField 来链接子和父,所以可以从父下移到子,如上面的例子。但是,这会占用为 ForeignKeyManyToManyField 关系的默认 related_name 值的名称。如果将这些类型的关系放在父模型的子类上,则 必须 在每个这样的字段上指定 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_name 添加到 customers 字段将解决错误:models.ManyToManyField(Place, related_name='provider')

代理模型

当使用 多表继承 时,将为模型的每个子类创建一个新的数据库表。这通常是所需的行为,因为子类需要一个地方来存储基类上不存在的任何附加数据字段。然而,有时,您只想更改模型的Python行为 - 也许是更改默认管理器,或添加一个新方法。

这是代理模型继承的用途:为原始模型创建 proxy。您可以创建,删除和更新代理模型的实例,并且所有数据将被保存,就像您使用原始(非代理)模型一样。不同之处在于,您可以更改代理中的默认模型排序或默认管理器,而无需更改原始模型。

代理模型声明为正常模型。你告诉Django它是一个代理模型,通过将 Meta 类的 proxy 属性设置为 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 属性 以与常规模型相同的方式

QuerySet 仍然返回请求的模型

每当你查询 Person 对象时,没有办法让Django返回一个 MyPerson 对象。 Person 对象的查询集将返回这些类型的对象。代理对象的整个点是,依赖于原始 Person 的代码将使用那些代码,您自己的代码可以使用您包括的扩展(没有其他代码依赖)。它不是一个方式来替换 Person (或任何其他)模型到处都有你自己的创造的东西。

基类限制

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

Changed in Django 1.10:

在早期版本中,代理模型无法继承多个共享同一父类的代理模型。

代理模型管理器

如果不在代理模型上指定任何模型管理器,它将从模型父类继承管理器。如果在代理模型上定义管理器,它将成为默认管理器,但是在父类上定义的任何管理器仍然可用。

继续我们上面的例子,你可以更改当你像这样查询 Person 模型时使用的默认管理器:

from django.db import models

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

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

如果要向代理添加新管理器,而不替换现有默认值,可以使用 定制管理器 文档中描述的技术:创建包含新管理器的基类,并在主基类后继承:

# 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

你可能不需要这么做,但是,当你这样做,这是可能的。

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

代理模型继承可能看起来非常类似于使用模型的 Meta 类上的 managed 属性创建非托管模型。

通过仔细设置 Meta.db_table,您可以创建一个非托管模型,它对现有模型进行阴影,并向其中添加Python方法。但是,这将是非常重复和脆弱,因为你需要保持两个副本同步,如果你做任何更改。

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

一般规则是:

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

  2. 如果你想改变一个模型的Python只有行为,但保留所有相同的字段,如原来的,使用 Meta.proxy=True。这将设置事务,使得代理模型是保存数据时原始模型的存储结构的精确副本。

多继承

正如Python的子类化一样,Django模型可以从多个父模型继承。请记住,正常的Python名称解析规则适用。特定名称(例如 )出现的第一个基类将是所使用的;例如,这意味着如果多个父节点包含 类,则只有第一个将被使用,并且所有其他父节点将被忽略。

一般来说,你不需要继承多个父母。主要用例在这里是有用的是“混入”类:添加一个特殊的额外字段或方法到每个继承混合的类。尝试保持您的继承层次结构尽可能简单和直接,以便您不必努力找出特定的信息来源。

注意,从具有公共 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:

class Piece(models.Model):
    pass

class Article(Piece):
    ...

class Book(Piece):
    ...

class BookReview(Book, Article):
    pass

不允许字段名称“隐藏”

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

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

Changed in Django 1.10:

添加了覆盖抽象字段的功能。

警告

模型管理器继承自抽象基类。覆盖由继承的 Manager 引用的继承字段可能会导致微妙的错误。见 自定义管理器和模型继承

注解

一些字段在模型上定义额外的属性,例如 ForeignKey 定义了附加到字段名称的 _id 的额外属性,以及外部模型上的 related_namerelated_query_name

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

覆盖父模型中的字段导致在诸如初始化新实例(指定在 Model.__init__ 中初始化哪个字段)和序列化等领域中的困难。这些是正常的Python类继承不必以相同的方式处理的特性,因此Django模型继承和Python类继承之间的区别不是任意的。

此限制仅适用于属于 Field 实例的属性。如果你愿意,可以覆盖正常的Python属性。它也仅适用于Python看到的属性的名称:如果您手动指定数据库列名称,则可以在子表和祖先模型中同时出现相同的列名以进行多表继承(它们是列在两个不同的数据库表中)。

如果你覆盖任何祖先模型中的任何模型字段,Django将提出一个 FieldError

组织包中的模型

manage.py startapp 命令创建包括 models.py 文件的应用程序结构。如果你有很多模型,在单独的文件中组织它们可能是有用的。

为此,请创建 models 包。删除 models.py 并创建一个具有 __init__.py 文件和用于存储模型的文件的 myapp/models/ 目录。您必须在 __init__.py 文件中导入模型。

例如,如果 models 目录中有 organic.pysynthetic.py

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

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

参见

模型参考

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