模型¶ T0>

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

基础:

  • 每个模型都是一个Python类,它是django.db.models.Model的子类。
  • 模型的每个属性都代表一个数据库字段。
  • 综上所述,Django为您提供了一个自动生成的数据库访问API;请参阅Making queries

快速示例

这个示例模型定义了一个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是模型的字段 每个字段被指定为一个类属性,每个属性映射到一个数据库列。

上面的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是从一些模型元数据中自动派生出来的,但是可以被覆盖 有关更多详细信息,请参见Table names
  • 一个id字段会自动添加,但是这种行为可以被覆盖。 请参阅Automatic primary key fields
  • 在这个例子中,CREATE TABLE SQL是使用PostgreSQL语法格式化的,但值得注意的是,Django使用了在您的settings file中指定的数据库后端。

使用模型

一旦你定义了你的模型,你就需要告诉Django你将使用这些模型。 通过编辑您的设置文件并更改INSTALLED_APPS设置来添加包含models.py的模块的名称。

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

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

当您将新应用程序添加到INSTALLED_APPS时,请确保运行manage.py migrate可选地为它们首先进行manage.py makemigrations

领域¶ T0>

模型中最重要的部分 - 也是模型唯一必需的部分 - 是它定义的数据库字段的列表。 字段由类属性指定。 小心不要选择与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(例如&lt; input type =“text”&gt; t2 >,<select>)。
  • Django管理员和自动生成的表单中使用的最小验证要求。

Django发布了几十种内置字段类型;您可以在model field reference中找到完整列表。 如果Django的内置函数没有做到这一点,你可以很容易地编写自己的字段。请参阅Writing custom model fields

字段选项

每个字段都采用一组特定于字段的参数(在model field reference中记录)。 例如, CharField (和它的子类) 需要一个max_length参数,该参数指定用于存储数据的VARCHAR 数据库字段的大小

还有一组可用于所有字段类型的通用参数。 所有都是可选的。 它们在reference中有完整的解释,但是这里有一个最常用的摘要:

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

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

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

选择

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

选择列表如下所示:

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

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

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

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'
默认
字段的默认值。 这可以是一个值或一个可调用的对象。 如果可调用,则每次创建新对象时都会调用它。
help_text
额外的“帮助”文本与窗体小部件一起显示。 即使您的字段没有在表单上使用,对于文档也是有用的。
首要的关键

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

如果你没有为模型中的任何字段指定primary_key=True,Django将自动添加一个IntegerField来保存主键,所以你不需要设置primary_key=True,除非您想覆盖默认的主键行为。 有关更多信息,请参阅Automatic primary key fields

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

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']>
独特
如果True,则该字段在整个表中必须是唯一的。

再次,这些只是对最常见的字段选项的简短描述。 详细信息可以在common model field option reference中找到。

自动主键字段

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

id = models.AutoField(primary_key=True)

这是一个自动递增的主键。

如果您想指定一个自定义主键,只需在其中一个字段上指定primary_key=True即可。 如果Django看到你明确地设置了Field.primary_key,它不会添加自动id列。

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

详细的字段名称

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

在这个例子中,详细名称是"人的 第一个 名称"

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会自动把它需要的第一个字母大写。

关系¶ T0>

显然,关系数据库的强大之处在于将表彼此关联。 Django提供了一些方法来定义三种最常见的数据库关系类型:多对一,多对多和一对一。

多对一关系

要定义多对一的关系,请使用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)
    # ...

您还可以创建recursive relationships(与自身具有多对一关系的对象),relationships to models not yet defined;有关详细信息,请参阅the model field reference

有人建议,但不是必须的,ForeignKey字段(上面例子中的manufacturer)的名称是小写字母的名称。 当然,只要你愿意,可以任意命名你想要的字段 例如:

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

也可以看看

ForeignKey fields accept a number of extra arguments which are explained in 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

有人建议,但不是必须的,上面例子中的ManyToManyFieldtoppings)的名称是描述相关模型对象集合的复数形式。

哪个模型具有ManyToManyField并不重要,但是您应该只将其放在其中一个模型中 - 而不是两个。

一般来说,ManyToManyField实例都应该在一个表单上进行编辑。 In the above example, toppings is in Pizza (rather than Topping having a pizzas ManyToManyField ) because it’s more natural to think about a pizza having toppings than a topping being on multiple pizzas. The way it’s set up above, the Pizza form would let users select the toppings.

也可以看看

完整的示例请参见Many-to-many relationship model example

ManyToManyField fields also accept a number of extra arguments which are explained in the model field reference. 这些选项有助于定义关系如何工作;都是可选的。

多对多关系上的额外字段

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

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

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

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)

在设置中介模型时,显式指定涉及多对多关系的模型的外键。 这个明确的声明定义了两个模型是如何相关的。

中间模型有几个限制:

  • Your intermediate model must contain one - and only one - foreign key to the source model (this would be Group in our example), or you must explicitly specify the foreign keys Django should use for the relationship using ManyToManyField.through_fields. 如果您有多个外键,并且没有指定through_fields,则会引发验证错误。 类似的限制适用于目标模型的外键(在本例中这将是Person)。
  • 对于通过中间模型与自身有多对多关系的模型,允许使用同一模型的两个外键,但它们将被视为多对多关系的两个(不同的)方面。 If there are more than two foreign keys though, you must also specify through_fields as above, or a validation error will be raised.
  • When defining a many-to-many relationship from a model to itself, using an intermediary model, you must use symmetrical=False (see the model field reference).

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

Unlike normal many-to-many fields, you can’t use add(), create(), or set() to create relationships:

>>> # The following statements will not work
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> 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 requires a positional argument: the class to which the model is related.

例如,如果您正在构建“地点”数据库,则可以在数据库中构建诸如地址,电话号码等相当标准的内容。 Then, if you wanted to build a database of restaurants on top of the places, instead of repeating yourself and replicating those fields in the Restaurant model, you could make Restaurant have a OneToOneField to Place (because a restaurant “is a” place; in fact, to handle this you’d typically use inheritance, which involves an implicit one-to-one relation).

ForeignKey一样,可以定义一个recursive relationship,并且可以引用references to as-yet undefined models

也可以看看

查看完整示例的One-to-one relationship model example

OneToOneField fields also accept an optional parent_link argument.

OneToOneField classes used to automatically become the primary key on a model. 这不再是真实的(虽然你可以手动传递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保留字(如joinwhereselect)被允许作为模型字段名称,因为Django转义所有数据库表名和每个基础SQL查询中的列名。 它使用特定数据库引擎的引用语法。

自定义字段类型

如果其中一个现有模型字段不能用于适合您的目的,或者如果您希望利用一些不太常见的数据库列类型,则可以创建自己的字段类。 Writing custom model fields中提供了创建自己的字段的全面介绍。

Meta options

通过使用内部的 Meta来提供模型元数据,如下所示:

from django.db import models

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

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

Model metadata is “anything that’s not a field”, such as ordering options (ordering), database table name (db_table), or human-readable singular and plural names (verbose_name and verbose_name_plural). 没有必要,向模型添加class Meta是完全可选的。

所有可能的Meta选项的完整列表可以在model option reference中找到。

模型属性

对象
模型的最重要的属性是Manager It’s the interface through which database query operations are provided to Django models and is used to retrieve the instances from the database. 如果未定义自定义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 '%s %s' % (self.first_name, self.last_name)

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

model instance reference有自动给每个模型methods automatically given to each model 你可以覆盖其中的大部分 - 参见下面的覆盖预定义的模型方法 - 但有几个你几乎总是想要定义:

__str __()

一个Python“魔术方法”,返回任何对象的字符串表示形式。 这就是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,那么保证你的代码在添加时会自动支持这些参数。

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

Note that the delete() method for an object is not necessarily called when deleting objects in bulk using a QuerySet or as a result of a cascading delete. 为了确保执行自定义的删除逻辑,可以使用pre_delete和/或post_delete信号。

Unfortunately, there isn’t a workaround when creating or updating objects in bulk, since none of save(), pre_save, and post_save are called.

执行自定义SQL

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

模型继承

Django中的模型继承与Python中普通类继承的工作方式几乎完全相同,但是页面开始处的基础仍然应该遵循。 这意味着基类应该是django.db.models.Model的子类。

您唯一需要做出的决定是,您是否希望父模型本身(使用自己的数据库表),还是只有通过子模型才能看到的公共信息的持有者。

在Django中有三种可能的继承类型。

  1. 通常情况下,您只需要使用父类来保存您不希望为每个子模型输入的信息。 这个类不会被孤立地使用,所以Abstract base classes就是你所追求的。
  2. 如果你要对现有的模型进行子类化(也许完全是另一个应用程序的东西 ) 并且希望每个模型都有自己的数据库表,Multi-table inheritance是一种方法。
  3. 最后,如果您只想修改模型的Python级行为,而不以任何方式更改模型字段,则可以使用Proxy models

抽象基类

当你想把一些共同的信息放入其他许多模型中时,抽象基类是很有用的。 你写你的基类,把abstract=True放在Meta类中。 这个模型将不会被用来创建任何数据库表。 相反,当它被用作其他模型的基类时,它的字段将被添加到子类的那些字段中。 抽象基类中的字段与子元素中的字段具有相同的名称(并且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模型将有三个领域:name, agehome_group CommonInfo模型不能用作普通的Django模型,因为它是一个抽象基类。 它不会生成数据库表或拥有管理器,也不能直接实例化或保存。

对于很多用途,这种类型的模型继承将正是你想要的。 它提供了一种在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类中包含是没有意义的。 For example, including db_table would mean that all the child classes (the ones that don’t specify their own Meta) would use the same database table, which is almost certainly not what you want.

多表继承

Django支持的第二种模型继承是当层次结构中的每个模型都是模型本身时。 每个模型对应于它自己的数据库表,可以单独查询和创建。 The inheritance relationship introduces links between the child model and each of its parents (via an automatically-created 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: ...>

However, if p in the above example was not a Restaurant (it had been created directly as a Place object or was the parent of some other class), referring to p.restaurant would raise a Restaurant.DoesNotExist exception.

The automatically-created OneToOneField on Restaurant that links it to Place looks like this:

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

您可以通过在Restaurantparent_link=True声明自己的OneToOneField来覆盖该字段。

Meta and multi-table inheritance

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

所以一个子模型不能访问它的父类的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 “) T6> T4>。

代理模式

当使用multi-table inheritance时,将为模型的每个子类创建一个新的数据库表。 这通常是所需的行为,因为子类需要一个地方来存储基类中不存在的任何附加数据字段。 然而有时候,你只想改变一个模型的Python行为 - 可能是改变默认管理器,或者添加一个新的方法。

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

代理模型被声明为正常模型。 您通过将Meta类的proxy属性设置为True来告诉Django它是代理模型。

例如,假设你想添加一个方法到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

The MyPerson class operates on the same database table as its parent Person class. 特别是,任何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

QuerySets still return the model that was requested

每当查询Person对象时,都无法使Django返回,比如说一个MyPerson对象。 A queryset for Person objects will return those types of objects. 整个代理对象的要点在于,依靠原始Person的代码将使用这些代码,并且您自己的代码可以使用您包含的扩展(不管其他代码依赖于哪个代码)。 这不是用你自己创造的东西来到处替换Person(或任何其他)模型的一种方法。

基类限制

代理模型必须从一个非抽象模型类继承。 您不能从多个非抽象模型继承,因为代理模型不提供不同数据库表中的行之间的任何连接。 A proxy model can inherit from any number of abstract model classes, providing they do not define any model fields. 代理模型也可以从任何数目的共享非抽象父类的代理模型继承。

代理模型管理器

如果您没有在代理模型中指定任何模型管理器,它将继承模型父项的管理器。 如果您在代理模型上定义了一个管理器,它将成为默认值,尽管父类上定义的任何管理器仍然可用。

继续上面的示例,您可以更改在查询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

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

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

代理模型继承与使用模型的Meta类中的managed属性创建非托管模型看起来非常相似。

仔细设置Meta.db_table,您可以创建一个非托管模型,该模型会隐藏现有模型并向其添加Python方法。 但是,如果您做出任何更改,则需要将这两个副本保持同步,这会非常重复且很脆弱。

另一方面,代理模型的行为与其所代理的模型完全相同。 他们总是与父模型同步,因为他们直接继承其领域和经理。

一般规则是:

  1. 如果您正在镜像现有的模型或数据库表,并且不想要所有原始数据库表列,请使用Meta.managed=False 该选项通常用于对不在Django控制之下的数据库视图和表进行建模。
  2. 如果您想要更改模型的仅Python行为,但保留与原始字段相同的所有字段,请使用Meta.proxy=True 这样设置就可以使代理模型成为保存数据时原始模型的存储结构的精确副本。

多重继承

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

一般来说,你不需要继承多个父母。 对于“mix-in”类来说,这是很有用的主要用例:为每个继承了混合的类添加一个特定的额外的字段或方法。 尽量保持你的继承层次结构尽可能简单直接,这样你就不用费劲去研究一个特定的信息来自哪里。

注意,从具有共同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 = 来删除。

警告

模型管理器是从抽象基类继承的。 覆盖由继承的Manager引用的继承字段可能会导致微妙的错误。 请参阅custom managers and model inheritance

注意

某些字段在模型上定义了额外的属性,例如一个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文件中导入模型。

For example, if you had organic.py and synthetic.py in the models directory:

MYAPP /模型/ __ init__.py
from .organic import Person
from .synthetic import Robot

Explicitly importing each model rather than using from .models import * has the advantages of not cluttering the namespace, making code more readable, and keeping code analysis tools useful.

也可以看看

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