【Django文档转译】第2章:模型层——第1节:模型(模块1:模型介绍)

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

基础:

每个模型都是一个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细分会被自动添加,但是这种行为可以被改写。请参见:替换主键细分。
在此示例中,SQL是使用PostgreSQL语法格式化的,但是值得注意的是Django使用了针对设置文件中指定的数据库后端定制的SQL 。CREATE TABLE

使用模型¶

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

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

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

当您向INSTALLED_APPS添加新的应用的时候,请重新运行:djadmin:manage.py migration ,则您也可以先使用以下命令先进行迁移。manage.py makemigrations

初步¶

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

细分类型¶

模型中每一个分区都应该是相应类的实例,Django利用这些细分类来实现以下这些功能。

字段类型用以指定数据库数据类型(如:INTEGER,VARCHAR,TEXT)
默认的HTML表单输入框</ ref / forms / widgets>(如:<input type =“ text”> )
用于Django admin和自动生成表单的基本验证。
Django内置了多种细分类型;您可以在模型分区参考中看到完整列表。如果Django内置类型不能满足您的需求,您可以轻松地编写自定义的细分类型;见:doc:/ howto / custom-model-fields。

初步选项¶

每一种类别都需要指定一些特定的参数(参考model field reference )例如::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-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会自动使用该变量的属性名作为该参数值,并且将下划线转换为空格。

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

ForeignKeyManyToManyField和,并且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提供了定义了几种最常见的数据库关联关系的方法:多对一,多对多,一对一。

多对一关系¶

定义一个多对一的关联关系,使用django.db.models.ForeignKey类。就和其他Field类别类型一样,只需要在你模型中添加一个该该类的属性。

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

例如,如果一个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)
    # ...

你也可以创建一个递归关系(一个模型与它本身有多对一的关系)和:ref:与尚未定义的模型的关系<lazy-relationships>;详情请见:ref:模型字段参考<ref-foreignkey>

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

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

多对多关系¶

定义一个多对多的关联关系,使用django.db.models.ManyToManyField类。就和其他Field类别类型一样,只需要在你模型中添加一个该该类的属性。

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

例如:如果Pizza含有多种可能存在于多个 中,并且每个 都有多个-那么可以这样表示这种关系: 也就是一种 PizzaTopping

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 当中(而不是Topping中有指向pizzas的ManyToManyField实例)因为相较于配料被放在不同的披萨当中,披萨当中有很多种配料更符合常理。按照先前说的,在编辑Pizza的表单时用户可以选择多种配料。

ManyToManyField字段还接受许多额外的参数,这些参数在模型字段参考中有解释。这些选项有助于定义关系的工作方式。所有都是可选的。

在多对多(many-to-many)关系中添加添加额外的属性细分¶

如果你只是想要一个减少记录披萨和配料之间混合和搭配的简单多对多关系,标准的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参数在多个外键当中手动选择一个外键,有如果外多个姐没有用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()
<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])

为什么?你不能简单的在Person和Group之间建立关系-你需要指定Membership模型当中需要的所有关于此后关系的细节信息。因此,在使用中间模型来定义多对多关系的时候这些方法无法使用。关系的唯一办法是创建中间模型的实例。

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


>>> 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模型,你也可以将一个指向Place OneToOneField放到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在模型分区命名方面只有两个限制。

一个类别的名称不能是Python保留字,因为这回导致Python语法错误。

class Example(models.Model):
    pass = models.IntegerField() # 'pass' is a reserved word!

一个细分名称不能包含连续的多个下划线,原因在于Django查询语法的工作方式。

class Example(models.Model):
    foo__bar = models.IntegerField() # 'foo__bar' has two underscores!

但是,这些限制是可以被解决的,因为分区名没要求和数据库列名一样db_column 。

SQL保留字,例如join,where或select,是可以被用在模型分区名当中的,因为Django在对重叠的SQL查询进行清洗了所有的数据库表名和分区名,通过使用特定数据库引擎的引用语法。

自定义的细分类型¶

如果已经存在的模型模型不能满足您的需求,或者您希望支持一些不太常见的数据库列类型,您可以创建自己的划分类。在:doc:` / howto / custom-model-fields中提供了创建自己的主轴的各方面内容。

Meta选项¶

使用内部Meta类来给模型有利元数据,就像:

from django.db import models

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

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

模型的元数据是指“所有不是字段的东西”,比如排序选项(ATTR:〜Options.ordering),数据库表名(db_table),或是人可读的单复数名(verbose_name和verbose_name_plural)都不是必须的,并且在模型当中添加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"

    @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的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().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)

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

批量操作中不会调用覆盖的模型方法

请注意,delete()当使用QuerySet或作为的结果批量删除对象时,不一定调用对象的方法。为了确保执行自定义的删除逻辑,您可以使用 和/或 发出信号。cascading deletepre_deletepost_delete

不幸的是,没有一个解决办法时 creating或 updating物体散装,因为没有的save(), pre_save和 post_save被调用。

执行自定义SQL¶

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

继承模型¶

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

您唯一需要做出的决定是,您是否希望父模型成为自己的模型(具有自己的数据库表),还是希望父模型只是仅通过子模型可见的公共信息的持有者。

在Django中,可以使用三种继承样式。

通常,您只想使用父类来保存不需要为每个子模型键入的信息。此类永远不会孤立地使用,因此您所追求的是Abstract基类。
如果您要继承现有模型(也许完全来自另一个应用程序),并希望每个模型都有自己的数据库表,则 多表继承是必经之路。
最后,如果您只想修改模型的Python级行为,而又不以任何方式更改models字段,则可以使用 Proxy模型。

抽象基类¶

当您要将一些公共信息放入许多其他模型时,抽象基类很有用。你写你的基类,并把abstract=True在元 类。这样,该模型将不会用于创建任何数据库表。相反,当将其用作其他模型的基类时,会将其字段添加到子类的字段中。

一个例子:

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继承¶

创建抽象基类后,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和related_query_name¶

如果在related_name或 related_query_name上使用ForeignKey或 ManyToManyField,则必须始终为该字段指定唯一的反向名称和查询名称。这通常会在抽象基类中引起问题,因为此类的字段包含在每个子类中,并且每次具有完全相同的属性值(包括 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字段 的反向名称为,common_childa_related反向查询名称为common_childas。
common.ChildB.m2m字段 的反向名称为,common_childb_related反向查询名称为 common_childbs。
最后,该rare.ChildB.m2m字段的反向名称为rare_childb_related,反向查询名称为 rare_childbs。

由您决定如何使用’%(class)s’and '%(app_label)s’部分来构造相关名称或相关查询名称,但是如果您忘记使用它,那么在执行系统检查(或运行migrate)时,Django将会引发错误。

如果未related_name 在抽象基类中为字段指定属性,则默认的反向名称将是子类的名称,后跟’_set’,就像通常直接在子类上声明该字段时那样。例如,在上面的代码中,如果related_name 省略属性,对于反向名称m2m字段将是 childa_set在ChildA壳体和childb_set用于ChildB 字段。

多表继承¶

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 例外。

在上自动创建的OneToOneField将 Restaurant其链接到,Place如下所示:

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

您可以通过声明自己重写场 OneToOneField与parent_link=True上Restaurant。

Meta和多表继承¶

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

因此,子模型无法访问其父模型的Meta类。但是,在少数情况下,子代会从父代继承行为:

如果子代未指定 ordering属性或 get_latest_by属性,它将从其父代继承这些行为。

如果父母有命令,并且您不希望孩子有任何自然命令,则可以显式禁用它:

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

继承和反向关系¶

因为多表继承使用隐式 OneToOneField链接子级和父级,所以可以从父级向下移动到子级,如上例所示。但是,这将使用related_name作为ForeignKeyand ManyToManyField关系的默认值 的名称。如果要将这些类型的关系放在父模型的子类上,则 必须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')

指定父链接字段¶

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

代理模式¶

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

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

代理模型的声明类似于普通模型。通过将类的proxy属性设置为,可以告诉Django这是一个代理模型。MetaTrue

例如,假设您要向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”属性:ref:和普通模型使用同样的方法<meta-and-multi-table-inheritance>。

QuerySet仍然返回请求的模型¶

MyPerson当您查询对象时,没有办法让Django返回一个Person对象。一种用于查询集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

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

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

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

通过仔细的设置,Meta.db_table您可以创建一个不存在的模型,该模型会遮盖现有模型并向其中添加Python方法。但是,这将非常重复且脆弱,因为如果进行任何更改,则需要使两个副本保持同步。

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

一般规则是:

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

多重继承¶

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

通常,您不需要从多个父母那里继承。有用的主要用例是“混入”类:向继承混入的每个类添加特定的额外字段或方法。尝试使您的继承层次结构尽可能简单明了,这样您就不必费力找出特定信息的来源。

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

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

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

class BookReview(Book, Article):
    pass

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

class Piece(models.Model):
    pass

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

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

class BookReview(Book, Article):
    pass

字段名称“隐藏”是不允许的¶

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

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

field_name = None

警告

模型管理器是从抽象基类继承的。覆盖被继承的对象引用的继承的字段 Manager可能会导致细微的错误。请参阅自定义管理器和模型继承。

注解

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

除非更改或删除了定义这些属性的字段,以便它们不再定义该额外属性,否则不能覆盖这些额外属性。

在父模型中覆盖字段会在诸如初始化新实例(指定要在中初始化哪个字段Model.init)和序列化等方面带来困难 。这些是普通Python类继承不必以完全相同的方式处理的功能,因此Django模型继承和Python类继承之间的区别不是任意的。

此限制仅适用于作为Field实例的属性 。如果需要,可以覆盖普通的Python属性。它也仅适用于Python所见的属性名称:如果您手动指定数据库列名称,则在多表继承的子模型和祖先模型中都可以显示相同的列名称(它们是列在两个不同的数据库表中)。

FieldError如果您覆盖任何祖先模型中的任何模型字段,则Django都会引发。

在包中组织模型¶

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

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

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

MYAPP /模型/ __ init__.py中有所¶

from .organic import Person
from .synthetic import Robot

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

from .models import *
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值