Django - 模型

参考文献: Django 模型

(基本上就是翻译下面英文的部分,没看懂的地方也挺多,欢迎交流!)

目录

模型

快速上手

使用模型

字段

字段类型

字段选项

自动设置主键

备注名

关联关系

Many-to-many relationships

many-to-many relationships 其余字段

One-to-one relationships

Models across files

Field name restrictions(字段名称限制)

Custom field types(自定义字段类型)

Meta options

Model attributes

objects

Model methods

Overriding predefined model methods(覆盖预定义模型方法)

Executing custom SQL

模型继承

Abstract base classes

Multi-table inheritance

Proxy Models

Multiple inheritance

不允许字段名称 “hiding”

Organizing models in a package


模型

模型是您的数据唯一而且准确的信息来源。它包含您正在储存的数据的重要字段和行为。一般来说,每一个模型都映射一个数据库表。

基础:

  • 每个模型都是一个 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_name 和 last_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” 是自动从某些模型元数据中派生出来,但可以被改写。有关更多详细信息,请参阅:表命名。
  • 一个 id 字段会被自动添加,但是这种行为可以被改写。请参阅:默认主键字段。
  • 本例中的CREATE TABLE SQL使用PostgreSQL语法进行格式化,但值得注意的是Django使用的SQL是针对设置文件中指定的数据库后端定制的。

使用模型

一旦你定义了你的模型,你需要告诉 Django 你准备*使用*这些模型。你需要修改设置文件中的 INSTALLED_APPS ,在这个设置中添加包含你 models.py 文件的模块的名字。

例如,如果模型位于你项目中的``myapp.models``中( 此包结构使用:djadmin:manage.py startapp`命令创建),:setting:`INSTALLED_APPS 应设置如下:

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

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


字段

模型中最重要的、并且也是唯一必须的是数据库的字段定义。字段在类中定义。定义字段名时应小心避免使用与 models
API</ref/models/instances>冲突的名称, 如 ``clean`, save, or ``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()

字段类型

模型中每一个字段都应该是相应类的实例, Django 利用这些字段类来实现下面这些功能。

可参考:字段类型 (自己翻译的,有不足欢迎交流)

  • 字段类型用以指定数据库数据类型(如:INTEGER, VARCHAR, TEXT)
  • 默认的HTML表单输入框</ref/forms/widgets>(如:<input type="text"><select>)
  • 用于Django admin和自动生成表单的基本验证。

Django内置了多种字段类型;你可以在模型字段参考<model-field-types>中看到完整列表。如果Django内置类型不能满足你的需求,你可以很轻松地编写自定义的字段类型;见:doc:/howto/custom-model-fields。


字段选项

每一种字段都需要指定一些特定的参数(参考 model field reference<model-field-types> ) 例如: :class:`~django.db.models.CharField (以及它的子类)需要接收一个 max_length 参数,用以指定数据库存储数据时用的 VARCHAR 大小。

一些可选的参数是通用的,可以用于任何字段类型,详情请见 :ref:`reference<common-model-field-options> `,下面介绍一部分经常用到的通用参数:

null
如果设置为 True , 当该字段为空时,Django会将数据库中该字段设置为 NULL 。默认为 False 。


blank
如果设置为 True ,该字段允许为空。默认为 False 。注意该选项与 False 不同, null 选项仅仅是数据库层面的设置,然而 blank 是涉及表单验证方面。如果一个字段设置为 blank=True ,在进行表单验证时,接收的数据该字段值允许为空,而设置为 blank=False 时,不允许为空。

choices

该参数接收一个可迭代的列表或元组(基本单位为二元组)。如果指定了该参数,在实例化该模型时,该字段只能取选项列表中的值。

一个选项列表:

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

每个二元组的第一个值会储存在数据库中,而第二个值将只会用于显示作用。

对于一个模型实例,要获取该字段二元组中相对应的第二个值,使用 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 字段,用于设置为主键,因此除非你想重写 Django 默认的主键设置行为,你可以不手动设置主键。详情请见 自动设置主键 。

主键字段是只可读的,如果你修改一个模型实例该字段的值并保存,你将等同于创建了一个新的模型实例。例如:

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,则此字段在整个表中必须是惟一的。
再次声明,以上只是一些通用参数的简略描述。你可以在 :ref:`common model field option reference<common-model-field-options> ` 中找到完整的介绍。也可以参考我翻译的这个:字段选项


自动设置主键

默认情况下, Django 会给每一个模型添加下面的字段:

id = models.AutoField(primary_key=True)

这是一个自增的主键。

如果你想指定设置为为主键的字段, 在你想要设置为主键的字段上设置 primary_key=True 选项。如果 Django 看到你显式的设置了 Field.primary_key ,将不会自动在表(模型)中添加 id 列。

每个模型都需要拥有一个设置了 primary_key=True 的字段(无论是显式的设置还是 Django 自动设置)。


备注名

除了 ForeignKey , ManyToManyField 和 OneToOneField ,任何字段类型都接收一个可选的参数 verbose_name ,如果未指定该参数值, 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, ManyToManyField 和 OneToOneField 接收的第一个参数为模型的类名,后面可以添加一个 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 值首字母大写,必要时 Djanog 会自动把首字母转换为大写。


关联关系

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

Many-to-one relationships
定义一个多对一的关联关系,使用 django.db.models.ForeignKey 类。就和其他 Field 字段类型一样,只需要在你模型中添加一个值为该类的属性。

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

例如,如果一个 Car 模型有一个制造者 Manufacturer,就是说一个 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 关系(一个模型与它本身有多对一的关系)和 :ref:`relationships to models not yet defined <lazy-relationships> ` ;详情请见 :ref:`the model field reference <ref-foreignkey> ` 。

建议设置 ForeignKey 字段(上例中的 manufacturer )名为想要关联的模型名,但是你也可以随意设置为你想要的名称,例如:

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

备注:

ForeignKey 字段还可以接收一些其他的参数,详见 the model field reference ,这些可选的参数可以更深入的规定关联关系的具体实现。

有关访问向后相关对象的详细信息,请参见下面的关系向后示例。

如要查看相关示例代码,详见 :doc:`Many-to-one relationship model example </topics/db/examples/many_to_one> ` 。

Many-to-many relationships

定义一个多对多的关联关系,使用 django.db.models.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 类一样,你也可以创建 recursive relationships 关系(一个对象与他本身有着多对多的关系)和 relationships to models not yet defined 关系。

建议设置 ManyToManyField 字段(上例中的 toppings )名为一个复数名词,表示所要光联的模型对象的集合。

对于多对多关联关系的两个模型,可以在任何一个模型中添加 ManyToManyField 字段,但只能选择一个模型设置该字段,即不能同时在两模型中添加该字段。

通常,许多 ManyToManyField 实例应该放在将要在表单上编辑的对象中。在上面的例子中,披萨中使用了浇头(而不是浇头上有一个披萨 ManyToManyField),因为一个披萨有多个浇头比将一个浇头放在多个披萨上更自然。按照上面的设置方式,Pizza表单允许用户选择浇头。

备注:如要查看完整示例代码,详见 Many-to-many relationship model example

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

many-to-many relationships 其余字段

当你只处理简单的多对多关系时,比如混合和匹配披萨和配料,一个标准的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)

当您设置中介模型时,您将显式地为涉及多对多关系的模型指定外键。这个显式声明定义了这两个模型之间的关系。

中间模型有一些限制:

  • 中间模型必须包含源模型的一个外键(在我们的示例中是Group),或者必须使用ManyToManyField.through_fields显式指定Django应该用于关系的外键。如果您有多个外键,并且没有指定through_fields,则会引发验证错误。类似的限制也适用于目标模型的外键(在我们的示例中是Person)。
  • 对于通过中介模型与自身具有多对多关系的模型,允许使用同一个模型的两个外键,但是它们将被视为多对多关系的两个(不同的)方面。但是,如果有两个以上的外键,还必须如上所述指定through_fields,否则将引发验证错误。
  • 在使用中介模型定义模型自身的多对多关系时,必须使用symmetrical=False(请参阅模型字段引用)。

既然已经设置了ManyToManyField来使用中介模型(在本例中是成员关系),那么就可以开始创建一些多对多关系了。您可以通过创建中间模型的实例来实现这一点:

>>> 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()来创建关系:

>>> # The following statements will not work
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> beatles.members.set([john, paul, ringo, george])

为什么?您不能仅仅创建一个人和一个组之间的关系,您需要为成员关系模型所需的关系指定所有的细节。简单的add、create和assignment调用没有提供指定这个额外细节的方法。因此,对于使用中间模型的多对多关系,它们是禁用的。创建这种类型关系的惟一方法是创建中间模型的实例。

出于类似的原因禁用了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 model:

>>> 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对象查询多对多反向关系:

>>> 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 relationships

要定义一对一关系,请使用OneToOneField。您可以像使用任何其他字段类型一样使用它:将它包含为模型的类属性。

当对象以某种方式“扩展”另一个对象时,这对于对象的主键非常有用。(啥意思?)

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

例如,如果您正在构建一个 places 的数据库,那么您将在数据库中构建非常标准的东西,比如地址、电话号码等。然后,如果您想在这些位置之上构建一个 restaurants 数据库,而不是在Restaurants model中重复自己和复制这些字段,您可以让餐馆有一个一对一的字段来放置(因为餐馆“是”一个地方,事实上,要处理这个问题,您通常会使用继承,它涉及到隐式的一对一关系)。

与使用外键一样,可以定义递归关系,并且可以对尚未定义的模型进行引用。(这个递归关系到底啥意思)

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

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

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


Models across files

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

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

Field name restrictions(字段名称限制)

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保留字(比如join、where或select)作为模型字段名,因为Django在每个底层SQL查询中转义所有数据库表名和列名。它使用特定数据库引擎的引用语法。


Custom field types(自定义字段类型)

如果现有的模型字段之一不能用于满足您的目的,或者希望利用一些不太常见的数据库列类型,则可以创建自己的field类。完全覆盖创建自己的字段中提供了编写自定义模型字段


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"

模型元数据是“任何不是字段的东西”,例如排序选项(排序)、数据库表名(db_table)或人类可读的单复数名(verbose_name和verbose_name_plural)。它们都不是必需的,并且向模型添加类 Meta 是完全可选的。

所有可能的 Meta 的完整列表可以在模型选项引用中找到。


Model attributes

objects

模型最重要的属性是 Manager 。它是向 Django 模型提供数据库查询操作的接口,被用于从数据库检索实例。如果没有定义自定义管理器,则默认名称为 object 。管理器只能通过模型类访问,而不能通过模型实例访问。


Model methods

在模型上定义自定义方法,以便向对象添加自定义“row-level”功能。虽然 Manager 方法倾向于做“table-wide”的事情,但是模型方法应该作用于特定的模型实例。

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

比如,这个例子就有一些自定义方法:

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

模型实例引用拥有自动提供给每个模型的完整方法列表。您可以覆盖其中的大多数,参见下面的覆盖预定义模型方法,但是有一些方法您几乎总是想要定义:

__str__()

一个Python“魔法方法”,返回任何对象的字符串表示形式。这就是 Python 和 Django 在需要强制一个模型实例并将其显示为普通字符串时将使用的方法。最值得注意的是,当您在交互式控制台或管理器中显示对象时,就会发生这种情况。你总是想要定义这个方法;默认值根本没有多大帮助。

get_absolute_url()

这告诉Django如何计算对象的URL。Django在它的管理界面中使用了这个方法,并且在任何时候它都需要为一个对象找到一个URL。任何具有唯一标识URL的对象都应该定义此方法。

Overriding predefined 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()

你也可以阻止save()

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.

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

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

注意:覆盖的模型方法不会在批量操作上调用,当使用 QuerySet 或由于级联删除而大量删除对象时,不一定要调用对象的delete()方法。要确保执行定制的删除逻辑,可以使用 pre_delete 和/或 post_delete 信号。不幸的是,在批量创建或更新对象时没有解决方案,因为没有调用save()、pre_save和post_save。


Executing custom SQL

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


模型继承

模型继承在 Django 中与普通类继承在 Python 中的工作方式几乎完全相同, 但也仍应遵循本页开头的内容. 这意味着其基类应该继承自 django.db.models.Model 。

您必须做出的惟一决定是,您是希望父模型具有它们自己的模型(具有它们自己的数据库表),还是父模型只是公共信息的持有者,这些公共信息只通过子模型可见。

Django中有三种可能的继承样式:

  1. 通常,您只想使用父类来保存不需要为每个子模型键入的信息。这个类永远不会单独使用,所以 Abstract base classes 是您所追求的。
  2. 如果您要子类化一个现有模型(可能完全来自另一个应用程序),并且希望每个模型都有自己的数据库表,那么可以采用Multi-table inheritance。
  3. 最后,如果您只想修改模型的python级行为,而不以任何方式更改models字段,那么您可以使用 Proxy models 。

Abstract base classes

当您希望将一些公共信息放入许多其他模型中时,抽象基类非常有用。您编写基类并将 abstract=True 放在 Meta 类中。然后,这个模型将不用于创建任何数据库表。相反,当它被用作其他模型的基类时,它的字段将被添加到子类的字段中。例如:

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

继承自抽象基类的字段可以用另一个字段或值覆盖,也可以用None删除。

对于许多用途,这种类型的模型继承正是您想要的。它提供了一种在Python级别提取公共信息的方法,同时仍然在数据库级别为每个子模型创建一个数据库表。

Meta inheritance

当创建抽象基类时,Django将基类中声明的任何 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 将意味着所有子类(不指定它们自己的元数据的类)将使用相同的数据库表,这肯定不是您想要的。

注意使用 related_name 和 related_query_name

如果在一个 ForeignKey 或 ManyToManyField 上使用 related_name 或 related_query_name,则必须始终为该字段指定惟一的反向名称和查询名称。这通常会在抽象基类中引起问题,因为该类上的字段包含在每个子类中,每次属性的值都完全相同(包括related_name和related_query_name)。

要解决这个问题,当您仅在抽象基类中使用related_name或related_query_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",
        related_query_name="%(app_label)s_%(class)ss",
    )

    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 字段的 reverse name 是 common_childa_related,reverse query name 是 common_childas。common.ChildB.m2m 字段的 reverse name 是 conmmon_childb_ralated,reverse query name 是 common_childbs。最后,rare.ChildB.m2m 字段的 reverse name 是 rare_childb_related,它的 reverse query name 是 rare_childbs。取决于您如何使用 '%(class)s' 和 '%(app_label)s' 部分来构造 ralated name 或 related query name,但是如果您忘记使用它,Django将在您执行系统检查(或 run migrate)时引发错误。

如果没有在抽象基类中为字段指定 related_name 属性,则默认的反向名称将是子类的名称后跟“_set”,就像直接在子类上声明字段一样。例如,在上面的代码中,如果省略了 related_name 属性,那么m2m字段的反向名称在ChildA中是childa_set,在ChildB中是childb_set。

Multi-table inheritance

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

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对象:

>>> 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.resturant 会引起 Restaurant.DoesNotExist exception。

自动创建的 OneToOneField on Restaurant链接到Place,看起来是这样的:

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

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

Meta and multi-table inheritance

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

因此子模型不能访问其父模型的 Meta 类,但是,在少数有限的情况下如子元素从父元素继承行为:如果子元素没有指定 order 属性或 get_latest_by 属性,那么它将从父元素继承这些属性。

如果父节点有 ordering,而您不希望子节点有任何自然顺序,则可以显式禁用它:

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

Inheritance and reverse relations

因为多表继承使用一个隐式的 OneToOneField 来链接子表和父表,所以可以从父表向下移动到子表,如上面的示例所示。但是,这将使用作为 ForeignKey 和 ManyToManyField 关系的默认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'.

按照如下方式向 customers 字段添加 related_name 将解决错误:

models.ManyToManyField(Place,related_name =“provider”)。

Specifying the parent link field

如前所述,Django 将自动创建一个 OneToOneField,将子类链接回任何非抽象的父模型。如果希望控制链接回父类的属性的名称,可以创建自己的 OneToOneField 并设置 parent_link=True,以指示您的字段是返回父类的链接。

Proxy Models

当使用多表继承时,将为模型的每个子类创建一个新的数据库表。这通常是所需的行为,因为子类需要一个位置来存储基类中不存在的任何其他数据字段。然而,有时您只想更改模型的Python行为,可能是更改默认的 Manager,或者添加一个新方法。

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

代理模型像普通模型一样声明。通过将 Meta 类的代理属性设置为 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>

你仍然可以使用一个代理模型来定义模型的默认排序方法。你也许不会想一直对 Persion 进行排序,但是通常情况下用代理模型根据 last_name 属性进行排序。这很简单:

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

现在普通的 Person 查询将是无序的,OrderedPerson 查询将按 last_name 排序。

代理模型继承 Meta 和普通模型使用同样的方法 <meta-and-multi-table-inheritance>。

queryset仍然返回所请求的模型

无论何时查询 Person 对象,Django 都不可能返回 MyPerson 对象。Person 对象的 queryset 将返回这些类型的对象。代理对象的全部意义在于,依赖于原始人员的代码将使用这些代码,而您自己的代码可以使用包含的扩展(无论如何没有其他代码依赖于这些扩展)。它不是用你自己创造的东西来取代任何地方的 Person (或其他任何模型)的方法。

Base class restrictions

一个代理模型必须且仅能继承一个非抽象模型类。你不能继承多个非抽象模型类,因为代理模型无法提供不同数据表的任何行间连接。一个代理模型可以继承任意数量的抽象模型类,假如他们没有定义任何的模型字段。一个代理模型也可以继承任意数量的代理模型,只需他们共享同一个非抽象父类。

代理模型管理器

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

继续上面的例子,当你像这样查询Person模型时,你可以改变默认的Manager:

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

通常情况下,你可能不需要这么做。然而,你需要的时候,这也是可以的。

proxy inheritance 和 unmanaged models之间的差异

代理模型继承看起来与使用模型的 Meta 类上的 managed attribute 创建 unmanaged 模型非常相似。

注意当设置 Meta.db_table 会创建一个 unmanaged 模型,对现有模型进行阴影处理,并向其添加Python方法。然而,这将是非常重复和脆弱的,如果您做任何更改您需要保持同步的两个副本。

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

一般规则是:

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

Multiple inheritance

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

一般来说,你不需要从多个父母那里继承。在主要用例中,这对于 mix-in 类很有用:向继承 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

不允许字段名称 “hiding”

在常规Python类继承中,允许子类覆盖父类的任何属性。在Django中,模型字段通常不允许这样做。如果非抽象模型基类有一个名为author的字段,则不能在继承自该基类的任何类中创建另一个模型字段或定义一个名为author的属性。

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

警告:模型管理器继承自抽象基类。重写由继承Manager引用的继承字段可能会导致细微的错误。参见自定义管理器和模型继承

注意:有些字段在模型上定义了额外的属性,例如,一个外键定义了一个附加了 _id 的额外属性,以及在外部模型上定义了 related_name 和 related_query_name。除非定义这些额外属性的字段被更改或删除,以便不再定义这些额外属性,否则无法覆盖这些额外属性。

覆盖父模型中的字段会导致一些困难,比如初始化新实例(指定在Model. __init__中初始化哪个字段)和序列化。这些特性是普通Python类继承不需要以完全相同的方式处理的,所以Django模型继承和Python类继承之间的区别不是任意的。

此限制仅适用于作为字段实例的属性。如果您愿意,可以重写普通Python属性。它还仅适用于Python所看到的属性的名称:如果手动指定数据库列名,则可以在多表继承的子模型和祖先模型中显示相同的列名(它们是两个不同数据库表中的列)。(没怎么看懂这个QAQ)

如果您覆盖任何祖先模型中的任何模型字段,Django将引发一个FieldError。


Organizing models in a package

manage.py startapp command 创建一个包含 models.py 文件的应用程序结构。如果您有许多模型,将它们组织到单独的文件中可能会很有用。

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

例如,如果您在 models 目录中有 organic.py 和 synthetic.py:

# myapp/models/__init__.py

from .organic import Person
from .synthetic import Robot

显式地导入每个模型,而不是使用 from .models import *,这样做的好处是不会混淆命名空间,使代码更具可读性,并使代码分析工具保持有用。

注释:模型参考,涵盖所有与模型相关的api,包括模型字段、相关对象和QuerySet。

猜你喜欢

转载自blog.csdn.net/obf13/article/details/88667940
今日推荐