模型

  • 1. 快速上手
  • 2. 使用模型
  • 3. 字段
    • 3.1 字段类型
    • 3.2 字段选项
    • 3.3 自动设置主键
    • 3.4 备注名
    • 3.5 关联关系
      • 3.5.1 多对一
      • 3.5.2 多对多
        • 多对多关系中额外字段
      • 3.5.3 一对一
    • 3.6 跨app引用模型
    • 3.7 字段名称限制
    • 3.8 自定义字段类型
  • 4.Meta选项
  • 5.模型属性
  • 6.模型方法
    • 6.1 覆盖预定义模型方法
    • 6.2 执行自定义SQL
  • 7.模型继承
    • 7.1 抽象基类
      • 7.1.1 Meta继承
      • 7.1.2 小心related_name和related_query_name
    • 7.2 多表继承
      • 7.2.1 Meta和多表继承
      • 7.2.2 继承和反向关系
      • 7.2.3 指定 parent_link 字段
    • 7.3 代理模型
      • 7.3.1 QuerySet仍然返回被请求的模型
      • 7.3.2 基类限制
      • 7.3.3 代理模型管理器
      • 7.3.4 代理继承和非托管模型之间的差异
  • 8. 多重继承
  • 9.不允许字段名称“隐藏”
  • 10.在包中组织模型

本章完


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

基础:

  • 每个模型都是一个 Python 的类,这些类继承 django.db.models.Model
  • 模型类的每个属性都相当于一个数据库的字段。
  • 综上所说,Django 给你一个自动生成访问数据库的 API;请参阅 执行查询。

1. 快速上手

这个样例模型定义了一个 Person类, 其拥有 first_namelast_name属性:

first_namelast_name 是 模型的字段。每个字段都被指定为一个类属性,并且每个属性映射为一个数据库列。
上面的 Person 模型会创建一个如下的数据库表:

一些技术上的说明:

  • 该表的名称 “myapp_person” 是自动从某些模型元数据中派生出来,但可以被改写。有关更多详细信息,请参阅:表名。
  • 一个 id 字段会被自动添加,但是这种行为可以被改写。请参阅:默认主键字段。
  • 此例中的建表语句使用了PostgreSQL语法,但是值得注意的是Django使用的SQL语句跟你在配置文件中指定的数据库有关。

2. 使用模型

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

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

当你在INSTALLED_APPS配置新的app后,请记住按顺序执行manage.py makemigrationsmanage.py migrate这两条命令。


3. 字段

模型中最重要的、并且也是唯一需要指定的应用于数据库的字段定义。字段在类中定义。定义字段名时应小心避免使用与 models API 冲突的名称, 如 clean,save,delete等。

举例:

from django.db import modelsclass 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()

3.1 字段类型

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

  • 字段类型用以指定数据库数据类型(如:INTEGER, VARCHAR, TEXT
  • 默认的HTML widgets,用来渲染form中的子部件(如:<input type="text"> <select>
  • 用于Django admin和自动生成表单的基本验证。

Django内置了多种字段类型;你可以在 模型字段参考中看到完整列表。如果Django内置类型不能满足你的需求,你可以很轻松地编写自定义的字段类型;见 自定义模型字段。

3.2 字段选项

每一种字段都需要指定一些特定的参数(参考 模型字段参考) 例如: CharField(以及它的子类)需要接收一个 max_length 参数,用以指定数据库存储数据时用的 VARCHAR 大小。

一些可选的参数是通用的,可以用于任何字段类型,详情请见 指南,下面介绍一部分经常用到的通用参数:

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

blank
如果设置为 True ,该字段允许为空。默认为 False 。

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

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

一个选项列表:

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

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

对于一个模型实例,要获取该字段二元组中相对应的第二个值,使用 get_FOO_display() 方法(FOO为字段名字)。例如:

from django.db import modelsclass 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 modelsclass 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,则该字段的值在整个表里面唯一。

再次声明,以上只是一些通用参数的简略描述。你可以在 字段选项列表 中找到完整的介绍。

3.3 自动设置主键

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

这是一个自增的主键。

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

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

3.4 备注名

(1)除了 ForeignKey ManyToManyFieldOneToOneField ,任何字段类型都接收一个可选的参数 verbose_name

如果未指定该参数值, Django 会自动使用该字段的属性名作为该参数值,并且把下划线转换为空格。

在该例中 first_name属性的备注名为person's first name:

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

在该例中,备注名为first name:

first_name = models.CharField(max_length=30)

(2)
ForeignKey, ManyToManyField 和 OneToOneField 接收的第一个参数为关联模型的类名,后面可以添加一个 verbose_name 参数:

一般情况下不需要将 verbose_name 值首字母大写,必要时 Djanog 会自动把首字母转换为大写。

3.5 关联关系

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

3.5.1 多对一

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

ForeignKey 需要一个必须的参数:和它关联的类的名字。

例如,如果一个 Car 模型 有一个制造者 Manufacturer ,就是说一个 Manufacturer 制造许多辆车,但是每辆车都属于某个特定的制造者。那么使用下面的方法定义这个关系:

from django.db import modelsclass Manufacturer(models.Model):# ...passclass 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 字段还可以接收一些其他的参数,详见 模型字段引用 ,这些可选的参数可以更深入的规定关联关系的具体实现。
有关反向查询(译者注:例如,一对多关系中,由一查多为反向查询,由多查一为正向查询),请见 反向查询。
如要查看相关示例代码,详见 多对一关系实例 。↑

3.5.2 多对多

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

ManyToManyField 类需要一个必须的参数:和它关联的类的名字。

例如:如果 Pizza 含有多种 Topping(配料),也就是一种Topping 可能存在于多个 Pizza 中,并且每个 Pizza 含有多种 Topping。那么可以这样表示这种关系:

from django.db import modelsclass Topping(models.Model):# ...passclass Pizza(models.Model):# ...toppings = models.ManyToManyField(Topping)

和 ForeignKey 类一样,你也可以创建 递归关系(一个对象与他本身有着多对多的关系)并且可以对尚未定义的模型进行引用。

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

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

通常来讲,在表单中,一个ManyToManyField 属性应该跟随关联的对象出现。上面的例子中,通常我们会这样想,是Pizza中有toppings(多种配料),而不是一种topping属于多个Pizzas。所以按照这种想法,在form表单中,应该是配料跟随Pizza对象出现。

参见
如要查看完整示例代码,详见 多对多关系实例。

ManyToManyField字段还有另一些参数,这些 参数 用来定义多对多关系该如何执行,都是可选的。

多对多关系中额外字段

当您只处理简单的多对多关系时,例如比萨饼和配料,ManyToManyField就可以完成了 。但是,有时您可能需要将数据与两个模型之间的关系相关联。

例如,考虑应用程序跟踪音乐家所属的音乐组的情况。一个人与他们所属的团体之间存在多对多的关系,因此您可以使用ManyToManyField来表示这种关系。但是,您可能希望收集成员的更多信息,例如此人加入该组的日期。

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

from django.db import modelsclass Person(models.Model):name = models.CharField(max_length=128)def __str__(self):return self.nameclass Group(models.Model):name = models.CharField(max_length=128)members = models.ManyToManyField(Person, through='Membership')def __str__(self):return self.nameclass 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)

译者注:其实就是用户通过through关键字自定义多对多关系中的第三张表

设置中间模型时,您明确指定多对多关系中涉及的模型的外键。此声明显式定义了两个模型的关联方式。

中间模型有一些限制:

  • 您的中间模型必须包含一个 且只有一个源模型的外键(在我们的示例中是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()
<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()创建关系:

>>> # 下面的语句不会生效
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> beatles.members.set([john, paul, ringo, george])

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

由于类似的原因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>]>>>> #  下面的语句不会生效,因为没有指出哪个membership 要被删除
>>> 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>]>

在使用中间模型时,您还可以查询其属性:

如果您需要访问会员资格,可以直接查询Membership模型:

# 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]>

访问同一信息的另一种方式是通过查询一个 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.'

3.5.3 一对一

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

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

OneToOneField 需要一个必须的参数:与模型相关的类的名字。

例如,如果您正在构建“place(地点)”数据库,您将在数据库中构建非常完整的内容,例如地址,电话号码等。然后,如果你想在这些地点建立一个餐馆数据库,而不是在Restaurant模型中复制并重复这些字段,你可以让Restaurant有一个OneToOneField字段并关联到Place(因为一个餐馆“是一个”地点";事实上,处理这通常使用 继承,它涉及隐式的一对一关系)。

与ForeignKey相同,可以定义递归关系并且可以对尚未定义的模型进行引用。

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

OneToOneField字段也接受可选的 parent_link参数。

OneToOneField类在模型中会自动变成主键。这不再有效(尽管你可以手动传入primary_key参数)。因此,现在单个模型上可以具有多个OneToOneField在类型的字段 。

3.6 跨app引用模型

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

from django.db import models
from geography.models import ZipCodeclass Restaurant(models.Model):# ...zip_code = models.ForeignKey(ZipCode,on_delete=models.SET_NULL,blank=True,null=True,)

3.7 字段名称限制

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查询中的所有数据库表名和列名。它使用特定数据库引擎的引用语法。

3.8 自定义字段类型

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

4.Meta选项

在模型类内定义一个Meta类,可以为模型类提供元数据,如下所示:

from django.db import modelsclass 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所有可能选项的完整列表可以在Meta选项中找到。

5.模型属性

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

6.模型方法

在模型上自定义方法,以向对象添加自定义“行级”功能。虽然Manager方法旨在执行“表内"的事情,但模型方法可以作用于特定的模型实例。

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

例如,此模型有一些自定义方法:

from django.db import modelsclass 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 datetimeif 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"@propertydef full_name(self):"Returns the person's full name."return '%s %s' % (self.first_name, self.last_name)

此示例中的最后一个方法是 property (也就是python中的property方法)。

model instance reference中给出了 模型类自动添加的方法 的完整列表。您可以覆盖其中的大多数 - 请参阅下面的 覆盖预定义模型方法 - 但有几个方法您几乎总是要自定义:

__str__()
Python“魔术方法”,返回任何对象的字符串表示形式。这是Python和Django在模型实例需要被强制并显示为纯字符串时将使用的内容。最值得注意的是,当您在交互式控制台或管理员中显示对象时会发生这种情况。

你总是想要定义这个方法; 因为默认的此方法没什么实质性的作用。

get_absolute_url()
这告诉Django如何计算对象的URL。Django在其admin管理界面中使用它,并且只需要它找出对象的URL。

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

6.1 覆盖预定义模型方法

还有另一组 模型方法,它们封装了一些您想要自定义的数据库行为。特别是你经常想改变save()和 delete()的工作方式。

您可以自由地覆盖这些方法(以及任何其他模型方法)来改变行为。

用于覆盖内置方法的经典用例是,如果您希望在save对象时发生某些事情。例如(参见 save()方法接受的参数的文档):

from django.db import modelsclass 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 modelsclass 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,则可以保证代码在添加时自动支持这些参数。

在批量操作中不会调用重写的模型方法
使用QuerySet在批量操作中删除对象或者级联删除的时候,对象的delete()方法不一定要调用。为确保执行自定义delete()方法,您可以使用 pre_delete或者post_delete信号。
不幸的是,在批量操作中没办法创建或更新对象,因为不会调用save(), pre_save和 post_save。

6.2 执行自定义SQL

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

7.模型继承

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

您必须做出的唯一决定是您希望父模型本身是模型(有自己的数据库表),还是父类只保存了一些通用信息,且子模型可见。

Django中有三种可能的继承方式。

  • 通常,您只想使用父类来保存您不希望为每个子模型键入的信息。这个类不会被单独使用,所以 抽象基类 就是你所追求的。
  • 如果你是现有模型的子类(可能是完全来自另一个应用程序的模型),并希望每个模型都有自己的数据库表,那么 多表继承 是最佳选择。
  • 最后,如果您只想修改模型的Python级行为,而不以任何方式更改模型字段,则可以使用 代理模型

7.1 抽象基类

当您想要将一些公共信息放入许多其他模型时,抽象基类非常有用。你写你的基类,并在Meta类里放上abstract=True。然后,此模型将不用于创建任何数据库表。相反,当它用作其他模型的基类时,其字段将添加到子类的字段中。

一个例子:

from django.db import modelsclass CommonInfo(models.Model):name = models.CharField(max_length=100)age = models.PositiveIntegerField()class Meta:abstract = Trueclass Student(CommonInfo):home_group = models.CharField(max_length=5)

Student模型将有三个属性:name,age和 home_group。CommonInfo模型不能用作普通的Django模型,因为它是一个抽象基类。它不生成数据库表或具有管理器,并且无法直接实例化或保存。

从抽象基类继承的字段可以使用其他字段或值覆盖,也可以使用None来删除。

对于许多用途,这种类型的模型继承将完全符合您的要求。它提供了一种在Python级别分解公共信息的方法,同时仍然只在数据库级别为每个子模型创建一个数据库表。

7.1.1 Meta继承

当创建抽象基类时,基类中的内部类Meta被声明为一个属性。如果子类没有声明自己的Meta 类,它将继承父类的Meta。如果孩子想要扩展父类的Meta类,它可以将其子类化。例如:

from django.db import modelsclass CommonInfo(models.Model):# ...class Meta:abstract = Trueordering = ['name']class Student(CommonInfo):# ...class Meta(CommonInfo.Meta):db_table = 'student_info'

Django确实对抽象基类的Meta类进行了一次调整:在安装Meta属性之前,它设置了abstract=False。这意味着抽象基类的子类本身不会自动成为抽象类。当然,您可以创建一个继承自另一个抽象基类的抽象基类。您只需要记住每次都明确设置abstract=True就可以了。

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

7.1.2 小心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 modelsclass 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 = Trueclass ChildA(Base):passclass ChildB(Base):pass

与另一个应用程序rare/models.py

from common.models import Baseclass ChildB(Base):pass

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

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

7.2 多表继承

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

from django.db import modelsclass 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")

译者注: 自己写了个例子有助于理解
(1)在place表中插入一条记录name=Bob's Cafe ,address=china-z
(2)在restaurant表中插入一条记录name=zb's CCx, address=china-ccb
在两张表中分别插入一条记录,结果如下

(3)可以看到,父类中的数据保存在父表中,子类中通过一个plact_ptr_id来引用父表中的id属性。
(4)查看restaurant表的定义

可以看到 Django为子类表自动生成了一个外键引用,指向父类,这也解释了开头说的 继承关系引入子模型与其每个父模型之间的链接(通过自动创建OneToOneField)


如果你有一个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 异常。

在Restaurant中自动创建的OneToOneField ,它链接到Place看起来像这样:

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

你可以按照上面的例子在Restaurant中重写这个一对一关系,并且指定parent_link=True 属性。

7.2.1 Meta和多表继承

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

因此,子模型无法访问其父级的Meta类。但是,有一些特殊的情况:如果子类没有指定 ordering属性或 get_latest_by属性,它将从其父类继承它们。

如果父类有一个ordering属性而你不希望孩子有任何自然顺序,你可以明确地禁用它:

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

7.2.2 继承和反向关系

因为多表继承使用隐式 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'.

添加related_name到customers字段将解决错误:models.ManyToManyField(Place, related_name='provider')

7.2.3 指定 parent_link 字段

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

7.3 代理模型

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

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

代理模型声明为普通模型。你通过设置模型的Meta类的proxy属性为True,告诉Django它是一个代理模型。

例如,假设您要向Person模型添加方法。你可以这样做:

from django.db import modelsclass Person(models.Model):first_name = models.CharField(max_length=30)last_name = models.CharField(max_length=30)class MyPerson(Person):class Meta:proxy = Truedef do_something(self):# ...pass

MyPerson类和父类Person操作数据库中的同一个表。特别是,任何新的实例Person也可以通过MyPerson来得到,反之亦然:

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

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

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

正常的Person查询将是无序的,OrderedPerson查询将按last_name的顺序排序。

译者投下一个例子

代理模型继承“Meta”属性:和普通模型使用同样的方法

7.3.1 QuerySet仍然返回被请求的模型

每当你查询MyPerson对象时,就是返回了Person对象。一个queryset返回这些Person类型的对象。代理对象的重点是依赖于原始代码,Person将使用这些代码,并且您自己的代码可以使用您包含的扩展(无论如何其他代码都不依赖)。它不是Person用你自己创造的东西替换任何地方(或任何其他)模型的方法。(翻译不太准)

7.3.2 基类限制

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

7.3.3 代理模型管理器

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

继续上面的示例,您可以更改查询Person模型时使用的默认管理器,如下所示:

from django.db import modelsclass NewManager(models.Manager):# ...passclass 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 = Trueclass MyPerson(Person, ExtraManagers):class Meta:proxy = True

译者注:此处投下一个使用实例

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

7.3.4 代理继承和非托管模型之间的差异

代理模型继承可能看起来与非托管模型(在模型类的Meta类中使用managed属性)非常相似。

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

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

一般规则是:

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

8. 多重继承

正如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):passclass 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

9.不允许字段名称“隐藏”

在普通的Python类继承中,子类允许覆盖父类的任何属性。在Django中,模型字段通常不允许这样做。如果非抽象模型基类具有一个author字段,则无法创建另一个author模型字段,子类中也不能有author字段。

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

警告
模型管理器继承自抽象基类。覆盖父类字段 (此字段由继承的 Manager引用),可能会导致细微的错误。请参阅 自定义管理器和模型继承。

注解
某些字段在模型中定义额外的属性,例如 ForeignKey定义了一个额外的属性 _id附加到字段名称,以及related_name和 related_query_name属性。
除非更改或删除定义它的字段以使其不再定义额外属性,否则不能覆盖这些额外属性。

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

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

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

10.在包中组织模型

manage.py startapp命令创建包含models.py文件的应用程序结构。如果您有许多模型,则在分开的文件中组织它们可能很有用。

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

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

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

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

Django 2.1.3 模型层 模型相关推荐

  1. CSS→代码语法、优先级、选择器、权值、字体文本、颜色、长度值、块状内联元素、盒模型、布局模型→层模型绝对相对定位、div排列、水平居中

    CSS全称为"层叠样式表 (Cascading Style Sheets)" 绝圣弃智,民利百倍: 绝仁弃义,民复孝慈: 绝巧弃利,盗贼无有. 此三者,以为文不足,故令有所属,见素 ...

  2. Django学习日志三:模型层

    Django学习日志三:模型层1 日志一我们学习了Django的配置,详见: Django学习日志一Django的配置 日志二我们学习了创建第一个django项目,详见: Django学习日志二--创 ...

  3. Django框架——模型层单表操作、模型层多表操作、模型层常用和非常用字段和参数、模型层进阶

    文章目录 1 模型层-单表操作 一 ORM简介 二 单表操作 2.1 创建表 1 创建模型 2 更多字段 3 更多参数 4 settings配置 5 增加,删除字段 2.2 添加表纪录 2.3 查询表 ...

  4. week11 day7 模型层

    week11 day7 模型层 模型层 一.django测试环境的搭建 二.单表查询关键字(精准查找) 必知必会13条 三.神奇的双下划线查询(范围查询) 四.图书管理系统的表设计 五.外键字段的增删 ...

  5. 【Django】模型层说明

    [Django模型层] 之前大概介绍Django的文章居然写了两篇..这篇是重点关注了Django的模型层来进行学习. ■ 模型定义 众所周知,Django中的模型定义就是定义一个类,其基本结构是这样 ...

  6. Django之模型层和ORM

    在之前的文章中已经介绍过数据库的操作使用,但是不够系统,今天就模型层和ORM做一个系统的介绍.所谓模型层就是主要负责和数据库之间进行数据交互的一个模块 Django连接mysql数据库,安装mysql ...

  7. Web开发-Django模型层

    Django模型层 简述 Django框架处理业务的逻辑一般如下(省略图,源于网络,侵删) 可以看到,Django自带了一套ORM机制,这也是Django框架的核心-"全面",将一 ...

  8. DJango周总结二:模型层,单表,多表操作,连表操作,数据库操作,事务

    django周复习二  1,模型层:   1单表操作:    13个必会操作总结     返回QuerySet对象的方法有     all()     filter()     exclude()   ...

  9. Django模板自定义标签和过滤器,模板继承(extend),Django的模型层

    上回精彩回顾 视图函数:request对象request.path 请求路径request.GET GET请求数据 QueryDict {}request.POST POST请求数据 QueryDic ...

最新文章

  1. 学习 Message(17): WM_SYSCOMMAND 消息[一]
  2. 佳能hdr_拥有高机动性,佳能推出两款UHDgc系列 4K广播级便携式变焦镜头
  3. 什么是流量劫持,如何防止流量劫持?
  4. 多版本opencv 兼容
  5. 使用Camel在来自不同来源的Solr中索引数据
  6. C/C++ realloc()函数解析
  7. hibernate mysql自定义变量_hibernate 跟 MySql in的参数变量
  8. Spring管理Hibernate
  9. 810B - 牛人是如何工作的
  10. 用友仓库档案boot直接访问html,图文解析用友仓库软件怎样设置档案?
  11. airflow部署和使用示例
  12. postman不跨域 本地开发跨域_为什么postman调接口不会跨域而浏览器会
  13. 江西省九江市瑞昌市高考成绩查询2021,2021江西省地区高考成绩排名查询,江西省高考各高中成绩喜报榜单...
  14. 100audio很好的免费音效网,音乐很值得学习
  15. Android:将鼠标右键点击事件设置为返回功能
  16. .Net MAUI 安卓状态栏透明、半透明、全屏
  17. 计算机里面不显示光驱位硬盘,悲剧了!win10下新加的光驱位机械硬盘无法识别...
  18. windows11编译OpenCV4.5.0 with CUDA(附注意事项)
  19. 二分查找和二分搜索树(含插入,查找的递归非递归)
  20. 微信小程序开发(一) 微信登录流程

热门文章

  1. 条条就要离开上海去南通工作了
  2. wampserver配置虚拟主机
  3. linux下npm安装的全局命令无法执行
  4. 无卡支付的几种模式(微信|支付宝|银联)
  5. 计算机一级考试实训在线做,程序设计能力 在线实训系统
  6. 二、Mingw下载安装
  7. Maven安装部署详细步骤(Win7系统)
  8. Windwos10启动后 Print Spooler 服务不能自动启动的解决方法
  9. 4月28日,邀您参加开鸿智谷教育在鸿OS发行版发布会
  10. 考研概率论与数理统计