Skip to content

Latest commit

 

History

History
978 lines (652 loc) · 61.9 KB

模型.md

File metadata and controls

978 lines (652 loc) · 61.9 KB

Models

原文:Models

模型是关于数据的单一,明确的信息来源。它包含了你存储的数据的主要字段和行为。一般情况下,每个模型映射到单个数据库表。

基本点:

  • 每一个模型都是一个Python类,它是django.db.models.Model的子类。
  • 模型的一个属性表示一个数据库字段。
  • 有了这一切,Django提供了一个自动生成数据库访问的API;见进行查询.

简单的例子

这个例子模型定义了一个Person,它有一个first_name和一个last_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你将使用这些模型。通过编辑你的设置文件,以及修改INSTALLED_APPS设置项以增加包含models.py文件的模块的名称来做到这点。

例如,如果你的应用的模型位于模块myapp.models (由manage.py startapp脚本为应用创建的包结构)中,INSTALLED_APPS部分应该读起来是这样的:

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

当你添加新的应用到INSTALLED_APPS中时,一定要运行manage.py migrate,可选地,首先使用manage.py makemigrations命令为它们制作迁移。

字段

一个模型最重要的部分,以及其唯一必须的部分,是它定义的数据库字段列表。字段通过类属性来指定。要小心,不要选择与模型API冲突的字段名,例如clean, save, 或者delete

例如:

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使用字段类类型来确定几件事:

  • 数据库列类型 (例如 INTEGER, VARCHAR)。
  • 当渲染一个表单域(例如 <input type="text">, <select>)时,使用的默认的HTML 控件
  • 最小的验证要求,用于Django管理和自动生成的表单中。

Django自带几十个内置的字段类型;你可以在Django模型字段参考中找到完整的列表。如果Django内置的类型达不到你的目的,你可以轻松的编写自己的字段类型;见编写自定义模型字段.

字段选项

每个字段接收特定字段参数的一个特定集合(见模型字段参考)。例如,CharField (及其子类)需要一个max_length参数,它指定了用来存储数据的VARCHAR类型的数据库字段的长度。

还有一个适用于所有字段类型的常见的参数的集合。该集合中的所有参数都是可选的。他们在参考中有充分的解释,但这里有最常用的那些的一个简单总结:

null

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

blank

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

注意,这不同于nullnull纯粹是数据库相关的,而blank则是验证相关的。如果一个字段被设置为blank=True,那么表单验证将允许项值为空。如果一个字段被设置为 blank=False,那么该字段是必传的。

choices

一个二元组的可迭代对象(例如,一个列表或元组),它被用作此字段的选项。若给定此参数,那么默认的表单空间将是一个选择框,而不是标准的文本字段,并且将会限制选择为给定的选项。

一个选项列表如下:

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

每一个元组的第一个元素是将要存储在数据库中的值,第二个元素显示在默认的表单控件中,或在ModelChoiceField中显示。给定一个模型对象的一个实例,可以使用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'

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 (无论是显式声明还是自动添加)。

详细的字段名

每一个字段类型,除了ForeignKey, ManyToManyFieldOneToOneField,都有一个可选的第一个位置参数:一个详细的名称。如果没有给出该详细名称,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)

ForeignKey, ManyToManyFieldOneToOneField要求其第一个参数是一个模型类,所以使用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)关系

要定义一个多对一关系,使用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字段接收许多额外的参数,它们在模型字段参考中有描述。这些选项帮助定义关系应该如何工作;它们所有都是可选的。

有关访问向后相关对象的详细信息,见下面的关系向后例子.

有关示例代码,见多对一关系模型

多对多(Many-to-many)关系

要定义一个多一对多的关系,使用ManyToManyField。你可以像其他Field类型那样使用它:通过作为模型的类属性包含它。

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

例如,如果一个 Pizza有多个Topping对象,也就是说,一个Topping可以在多个pizza上,而每个Pizza有多种topping,这里是你如果表示的:

from django.db import models

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

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

正如ForeignKey,你也可以创建递归关系 (一个与自身是多对多关系的对象)以及尚未定义的模型关系模型字段参考以了解详情。

建议,但不要求,一个ManyToManyField对象的名称 (上面例子中的toppings)是一个描述相关模型对象集合的复数。

哪一个模型拥有ManyToManyField对象并无关系,但是你应该将其放在其中一个模型中,而不是两个都放。

一般来说,ManyToManyField实例应该放在将在表单上编辑的对象中。在上面的例子中,toppingsPizza中 (而不是Topping有一个pizzas ManyToManyField ),因为,应该是更自然地想到一个pizza拥有toppings,而不是一个topping在多个pizza上。根据上面设置的方式,Pizza表单将让用户选择toppings。

另见

多到多关系模型例子以获得一个完整的例子。

ManyToManyField字段也接收一些额外的参数,在模型字段参考中对它们进行了解释。这些选项帮助定义关系应该如何工作;它们所有都是可选的。

多对多关系的额外字段

当你只处理简单的多对多关系,如混合和匹配pizzas和toppings,一个标准的 ManyToManyField是你所需要的。但是,有时你可能需要将数据与两个模型之间的关系联系起来。

例如,考虑一个应用程序跟踪音乐家属于的音乐团体的情况。一个人与其所在团队之间有一种多对多的关系,所以你可以使用一个ManyToManyField来表示这种关系。然而,你可能要收集很多有关此关系的细节有关,如在此人加入此团体的日期。

对于这些情况,Django允许你指定将用于管理多对多关系的模型。然后,您可以把额外的字段放在中间模型中。中间模型通过使用through参数指向作为中间媒介的模型与ManyToManyField相关联。对于我们的音乐家例子,代码会是这个样子:

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)

当您设置的中介模式时,您明确指定拥有多对多关系的模型的外键。这种显式声明定义了两种模式是如何相关的。

在中间模式中有一些限制:

  • 您的中间模型必须包含一个,并且只有一个,到源模型(将是我们的例子中的Group)的外键,或者你必须使用ManyToManyField.through_fields明确指定Django应该对此关系使用的外键。如果你有一个以上的外键,并且未指定through_fields,将引发一个验证错误。类似的限制适用于到目标模型的外键(将是我们的例子中的Person)。
  • 对于通过中介模型拥有对其自身多对多关系的模型,允许到相同模型的两个外键,但他们将被视为多对多关系的两个(不同的)方面。如果有超过两个外键,你还必须如上指定through_fields,或将引发一个验证错误。
  • 当定义一个模型到其自身的一个多对多关系时,使用一个中介模型,你必须使用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()
[<Person: Ringo Starr>]
>>> ringo.group_set.all()
[<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()
[<Person: Ringo Starr>, <Person: Paul McCartney>]

不同于一般的多对多字段,你不能使用add, create,或者赋值(例如,beatles.members = [...])来创建关系:

# 无效
>>> beatles.members.add(john)
# 也无效
>>> beatles.members.create(name="George Harrison")
# 还是无效
>>> beatles.members = [john, paul, ringo, george]

为什么呢?你不能只是创建一个Person和一个Group直接的关系 - 你需要为此关系指定Membership模型所需的所有细节。简单add, create和赋值调用不提供指定此额外细节的方法。结果是,它们对于使用的中间模型的多对多关系无效。建立这种关系的唯一方法是创建中间模型的实例。

remove()方法出于同样的原因被禁用。然而,clear()方法可以用来去除一个实例的所有多对多关系:

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

一旦你通过创建中介模型的实例来建立多对多关系,你就可以发出查询。正如正常的多对多关系,您可以使用多对多相关模型的属性进行查询:

# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
[<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))
[<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.'

Another way to access the same information is by querying the 另一种访问相同信息的方式是从一个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.'

一对一(One-to-one)关系

要定义一个一对一关系,使用OneToOneField。你可以像使用其他Field类型一样使用它:通过作为模型的一个类属性包含它。

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

OneToOneField需要一个位置参数:该模型相关的类。

例如,如果你正在建立一个“地方”数据库,你将在数据库中建立相当标准的东西,如地址,电话号码等。然后,如果你想在这些地方上建立一个餐馆数据库,你可以让Restaurant有一个到PlaceOneToOneField(因为一个餐馆“是一个”地方;事实上,要处理它,你通常会使用继承,这涉及到一个隐含的一对一关系),而不是在Restaurant模型中重复复制这些字段。

正如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!
  1. 由于Django的查询查找语法的工作方式,字段名不能包含排成一排的多个下划线。例如:
class Example(models.Model):
    foo__bar = models.IntegerField() # 'foo__bar' has two underscores!

虽然,这些限制可以一起工作,因为你的字段名不一定要匹配你的数据库列名。见db_column选项。

SQL保留字,如join, where或者select,都允许作为模型字段的名称,因为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_name以及verbose_name_plural)。它们都不是必选的,并且添加class Meta到一个模型是完全可选的。

所有可能的Meta选项的完整列表可见模型选项参考

模型属性

objects

一个模型最重要的属性是 Manager。它是提供给Django的数据库查询操作,并被用于从数据库中检索实例的接口。如果没有定义自定义的Manager,那么默认名为 objects。管理器(Manager)只能通过模型类进行访问,而不是模型实例。

模型方法

定义一个模型的自定义方法以添加自定义的“行级”功能到你的对象。而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)

模型实例参考有一个完整的自动提供给每个模型的方法的列表。 你可以覆盖它们大部分方法 – 见下面的重写预定义的模型方法,但也有一对你几乎总是想定义的方法:

__str__() (Python 3)

一个Python“魔术方法”,它返回任何对象的unicode的“代表”。每当一个模型实例需要被强制转换,并显示为纯字符串,Python和Django就会使用它。最值得注意的是,当你在一个交互式控制台或管理(admin)中显示一个对象时也会出现这种情况。

你总是会想要定义这个方法的; 默认并不非常有用。

__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,可以保证在添加新参数时,你的代码会自动支持这些参数。

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

注意,当使用查询集批量删除对象或者作为cascading delete的结果时,一个对象的delete()方法不一定被调用。为了确保执行自定义的删除逻辑,你可以使用pre_delete和/或者 post_delete信号。

不幸的是,当批量creating或者updating对象时,并没有一种解决方法,因为不会调用save(), pre_save, 和 post_save

执行自定义SQL

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

模型继承

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

你必须做出的唯一决定是你是否想父模型对自身来说是模型(有自己的数据库表),或者父模型只是普通信息的持有者,这些只能通过子模型可见。

Django中有三种可能的继承风格:

  1. 通常情况下,你只需要使用父类来保存你不希望为每个子模型都打一遍的信息。这个类是不会孤立地被使用的,所以抽象基类是你的选择。
  2. 如果您继承现有模型(可能是完全另一个应用程序中的模型),并希望每个模型都有自己的数据库表,那么多表继承是你的选择。
  3. 最后,如果你只是想要修改模型Python级别的行为,而不以任何方式修改模型的字段,那么你可以使用 代理模型

抽象基类

当你想将一些常用的信息放到其他一些模型中时,抽象基类是有用的。编写你的基类,并把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_groupCommonInfo模型不能用作正常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类中并无任何意义。例如,包含db_table意味着所有的子类(那些没有指定自己的Meta的类)将使用同一个数据库表,这是几乎可以肯定不是你想要的。

小心related_name

如果你在一个ForeignKeyManyToManyField上使用related_name属性,那么你必须总是为该字段指定一个唯一的反向名称。这通常会导致抽象基类的一个问题,因为该类的每一个子类每次都会包含这些字段,并且字段值完全相同(包括related_name)。

要解决此问题,当你(只)在一个抽象基类中使用related_name时,名称的一部分应该包含'%(app_label)s''%(class)s'

  • '%(class)s'会被使用此字段的子类的小写名所替代。
  • '%(app_label)s'会被包含该子类的应用的小写名所替代。每一个安装的应用名必须唯一,且每一个应用中的模型类名也必须唯一,因此,所得到的名最终将会不同。

例如,给定一个应用的common/models.py:

from django.db import models

class Base(models.Model):
    m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")

    class Meta:
        abstract = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass

和另一个应用的rare/models.py:

from common.models import Base

class ChildB(Base):
    pass

common.ChildA.m2m字段的反向名将是common_childa_related, 而common.ChildB.m2m字段的反向名将是common_childb_related,最后,rare.ChildB.m2m字段的反向名将是rare_childb_related。 这取决于你如何使用'%(class)s''%(app_label)s部分来构建你的相关名称,但是如果你忘记使用它,当你执行系统检查(或运行migrate)时,Django将会引发错误。

若没有为一个抽象类的一个字段指定related_name属性,那么默认的反向名将是``子类名__set,如果你直接在子类中声明该字段,那么它通常会是这样的。例如,在上面的代码中,如果省略[related_name`](https://docs.djangoproject.com/en/1.9/ref/models/fields/#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name")属性,那么`m2m`字段的反向名在`ChildA`中将是`childa_set`,而在`ChildB`中将是`childb_set`。

多表继承

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并不是一个Restaurant(它直接由Place对象创建,或者是其他类的父类),那么p.restaurant将会引发一个Restaurant.DoesNotExist异常。

Meta和多表继承

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

因此,一个子模型并没有访问其父类的Meta类的权利。但是,在少数有限的情况下,子类从父类中继承行为:如果子类不指定ordering 属性或get_latest_by属性,它会从其父继承这些。

若果父类有一个ordering,而你不想让子类有任何自然的顺序,你可以显式禁用它:

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

继承和反向关系

由于多表继承使用隐式的OneToOneField来链接父类和子类,因此有可能从父类下移到子类,正如上面的例子那样。然而,这将占用用于ForeignKeyManyToManyField关系的默认值。如果你将这些类型的关系放到该父类模型的子类中,那么你必须为每个这样的字段指定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').

指定父链接字段

As mentioned, Django will automatically create a 如前所述,Django将自动创建一个OneToOneField用于链接你的子类到任何非抽象父模型。如果你想要控制链接到父类的属性名,那么你可以创建自己的OneToOneField,然后设置parent_link=True,以表明你的字段是链接到父类的。

代理模型

当使用多表继承时,会为模型中的每个子模型创建一个新的数据库表。这通常是所期望的行为,因为子类需要一个地方来存储任何在基类中不存在的附加数据字段。但是有时候,你只想要更改模型的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

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排序。

查询集仍然会返回所请求的模型

没有办法让Django返回,比如说,无论何时你查询Person对象,Django都没法返回一个MyPerson对象。一个Person对象的查询集将返回这些类型的对象。代理对象的全部要点是依托原始的Person的代码将使用这些而你自己的代码可以使用你包括的扩展(不管怎样,没有其他代码所依托的)。这不是一个用你自己创造的东西到处取代Person(或其他任何)模型的方法。

基类限制

代理模型必须只有一个非抽象模型类继承。你不能继承多个非抽象模型,因为代理模型不提供任何不同数据库表中的行之间的连接。代理模型可以继承自任意数量的抽象模型类,但前提是它们没有定义任何模型字段。

代理模型管理

如果没有在一个代理模型上指定任何模型管理者,那么它会继承其父模型的管理者。如果你在该代理模型上定义了一个管理者,它将成为默认的管理者,虽然在父类中定义的任何管理者仍然可用。

继续我们上面的例子,当你像这样查询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.managed=False指定模型上的模型字段。你可以使用精心设置的Meta.db_table来创建一个掩盖(shadow)现有模型的非托管模型,并为它添加Python方法。然而,当你需要保持同步两个副本时,如果你做任何更改,这将是非常重复及脆弱的。

对代理模型更重的另一个不同之处是,如何处理模型管理者。代理模型是用于表现得完全像他们进行代理的模型。因此,它们继承父模型的管理者,包括默认管理者。在正常的多表模型继承的情况下,子类不继承父类的管理者,因为当涉及额外的字段时,自定义的管理者并不总是适合的。管理者文档有更多关于后一种情况的细节。

当实现这两个特征时,会尝试将它们放进一个单选项。结果是,一般情况下与继承的互动,特殊情况下与管理的互动,会让API非常复杂,并且可能很难理解和使用。最终,在任何情况下都需要这两种选择,所以目前出现了选项的分离。

所以,一般的规则是:

  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:

class Piece(models.Model):
    pass

class Article(Piece):
    ...

class Book(Piece):
    ...

class BookReview(Book, Article):
    pass

不允许字段名“hiding”

在普通的Python类继承中,允许一个子类覆盖父类中的任何属性。在Django中,对于那些是Field实例的属性是不允许的(至少,不是现在)。如果一个基类有一个名为author的字段,那么在继承该基类的任何类中,你都不能创建另一个名为author的模型字段。

在父模型中覆盖字段会带来某些领域的困难,例如初始化新实例(指明哪些字段正在Model.__init__中被初始化)和系列化。这些都是普通的Python类继承没有以同样的方式来处理的特点,所以Django模型继承和Python类继承之间的差别并不是随意的。

这种限制仅适用于是 Field实例的属性。如果你想,普通的Python属性可以被覆盖。它也只适用于Python认为的属性名:如果你手动指定数据库列名,那么对于多表继承,你可以让相同的列名都出现在子模型和父模型中(它们是在两个不同的数据库表的列)。

如果你覆盖任何祖先模型中的任何模型字段,那么Django会引发一个FieldError错误。

另见

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