您的位置:首页 > 编程语言 > Go语言

Django教程之十一-----模型

2017-09-13 10:03 351 查看
模型是关于你数据的单独的,明确的信息源。它包含你储存数据的重要的字段和行为。通常的,每个模型都映射一个单独的数据表。

基本:

每个模型都是继承django.db.models.Model的Python子类
每个模型的属性都表示一个数据库字段。
使用所有这些,Django提供给你一个自动生成数据库访问API;阅读<进行查询>。

1. 简单的例子

这个例子模型定义了一个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使用了在你设置文件中指定的数据库后端。

2. 使用模型

一旦你定义了你的模型,你需要告诉Django你正在使用模型。通过编辑你的设置文件并且改变INSTALLED_APPS设置来添加包含你的models.py的模型的名字来完成这个。

例如,如果你应用的模型在模型文件myapp.models(使用manage.py startapp脚本为你的应用创建的包结构),INSTALLED_APPS应该读取,部分的:
INSTALLED_APPS = [
#...
'myapp',
#...
]

当你在INSTALLED_APPS中添加新的应用时,确保运行manage.py migrate,或者先执行manage.py makemigrations来进行数据库迁移。

3. 字段

一个模型最重要的部分 -- 一个模型唯一需要的部分 -- 是他定义的数据字段的列表。字段由类属性指定。要小心不要选择和models 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()

3.1 字段类型 

你模型中的每个字段都应该是适当的字段类的实例。Django使用字段类类型来定义一些东西:

列类型,告诉数据库存储的数据的类型(例如 INTEGER,VARCHAR,TEXT)。
当渲染一个窗体字段时默认的HTML 小部件(例如 <input type = "text">,<select>)
用于Django管理和自动生成表单的最小的验证需求。
Django拥有数十种内置字段类型;你能在<模型字段指引>里找到完整的列表。如果Django内置的不满足,你可以很容编写你自己的字段;阅读<编写自定义模型字段>。



3.2 字段选项

每个字段都需要指定字段的参数集(参阅文档<模型字段指引>)。例如,CharField(和它的子类)需要一个max_length参数来指定Varchar数据库字段的大小来储存数据。

也有用于所有字段类型的通用参数。都是可选的。在<参考文献>中有完整的解释,但是这里有最常用的简单总结:
null
如果是True,Django将会在数据库中储存控制作为NULL。默认是False。
blank
如果是True,字段允许为空。默认是False。
注意这个null是不同的。null是完全的数据相关,然而blank是验证相关。如果一个字段有blank=True,表单验证将会允许输入一个空值。如果一个字段有blank=False,字段将会是必须的。
choices
2个元组的迭代器为这个字段使用choices。如果指定了这个,默认的窗体小部件将会是一个选择框而不是标准的text字段并且会用给定的选项来限制选择。

一个choices列表是这样:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
在每个元组中的第一个元素将会被储存到数据库。第二个元素将会被窗口小部件显示或在一个
ModelChoiceField。给定一个模型实例,可以使用get_F00_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)
<QuerySet ['Apple', 'Pear']>
unique
如果是True,这个字段在这个表必须是唯一的。

再次,这只是最常用的字段选项的简短描述。阅读<通用模型字段选项指引>来获得全部内容。

3.3 自动主键字段

默认的,Django提供给每个模型下列字段:
id = models.AutoField(primary_key=True)

这是一个自增长主键。

如果你想指定一个自定义主键,只要在你的一个字段上指定primary_key = True就可以了。如果Django知道你明确的设置了Field.primary_key,它将不会添加自动id列。

每个模型都确切的需要一个字段拥有primary_key=True(要么明确指定或者自动添加)。

3.4 详细字段名称

每个字段类型,除了ForeignKey,ManyToManyField和OneToOneField,需要一个可选的第一位置参数 -- 一个详细的名称。如果没有给定详细的名称,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的第一个字母不要大写。当需要的时候,Django将会自动大写第一个字母。

3.5 关系

很明显,关系型数据库的强大在于将表彼此关联。Django提供方式定义数据库关系中的3个最常用的类型:多对一,多对多和一对一。

多对一关系

为了定义一个多对一关系,使用django.db.models.ForeignKey.你就像任何其他Field类型一样使用它:通过作为你模型的类属性来包含它。

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

例如,如果一个Car模型有一个Manufactures -- 那就是说,一个Manufactures生产多个cars但是每一个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字段接受许多在<模型字段指引>中解释的额外的参数。这些选项帮助定义关系应该如何工作;所有都是可选的。

访问后端相关对象,阅读<遵循后端例子关系>

想要样本代码,阅读<多对一关系模型例子>

多对多关系

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

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

例如,如果一个Pizza有很多Topping对象 -- 那就是说,一个Topping能出现在多个pizzas中并且每个Pizza有多个Toppings -- 下面展示如何使用:
from django.db import models

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

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

与ForeignKey一样,你可以创建递归关系(对自己有多对多关系的对象)和未定义关系模型。

推荐,但不是必须的,一个ManyToManyField(上例中的toppings)的名称是一个相关模型对象集的复数描述。

哪个模型有ManyToManyField并没有关系,但你应该只将它放在其中一个模型中 -- 而不是2个都放。

通常的,ManyToManyField实例应该在一个窗体的对象上被编辑。在上例中,Pizza中的toppings(而不是Toppings有pizzas ManyToManyField)因为很自然的想到一个批萨有很多配料而不是一个配料在很多批萨上。按照上面的方法设置,Pizza窗体将会让用户选择配料。
另请参见

阅读<多对多关系模型例子>来获得完整的例子。

ManyToManyField同样接受来自于<模型字段指引>中解释的许多额外参数。这些选项帮助定义关系应该如何工作;所有都是可选的。

多对多关系的额外字段

当你只需要处理简单的多对多关系例如混合或者匹配批萨和配料时,一个标准的ManyToManyField就是所有你需要的。然而,有时你需要将数据在2个模型之间关联起来。

例如,考虑一个应用程序表明音乐家所属音乐团体的情况。在人和他们作为成员所在团体之间有一个多对多关系,所以你可以使用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)

当你建立中间模型,你明确的为涉及多对多关系的模型指定了外键。这个明确的描述定义了2个模型是如何关联的。

对于中间模型,有几点限制:

你的中间模型必须包含一个 --并且只能是一个 -- 指定源模型的外键(在上例中是Group),或者你必须明确指定Django使用ManyToManyField用于关系的外键。如果你有超过一个外键并且through_fields没有指定,一个校验错误将会被抛出。对于目标模型中外键的一个相似的限制(在我们例子中的Person)
对于一个通过中间模型对自己本身有一个多对多关系的模型,对相同模型的2个外键是允许的,但他们将会被当成是2种(不同)边的多对多关系。如果有超过2个外键,你也必须向上面一样指定through_fields,否则将会抛出校验错误。
当给一个模型自己定义多对多关系是,使用一个中间模型,你必须使用symmetrical=False(阅读<模型字段指引>)
既然你使用中间模型(Membership,在这个例子中)建立了你的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])

为什么?你不能在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>]>
>>> # 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对象中查询多对多反向关系:
>>> 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.'


一对一关系


为了定义一个一对一关系,使用OntToOneField。你可以向其他任何Field类型一样使用它:通过作为你的模型的类属性来包含它。

当那个对象以同样的方式继承另一个对象时,在主键上这将是很有帮助的。

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

例如,如果你创建了一个数据库“places”,你将创建十分标准的事情例如地址,电话号码等,在那个数据库。然后,如果你想在places上创建一个餐馆的数据库,不用在Restauant模型中重复你和复制这些字段,你可以让Restaurant拥有一个对Place的OneToOneField(因为一个餐馆“就是”Place;事实上,处理这个,使用继承更好,它包含一个隐式的一对一关系)。

就像ForeignKey一样,递归关系能够定义并且对尚未定义模型的引用也可以执行。
另请参见

阅读<一对一关系模型例子>来获得完整的例子

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

OneToOneField类过去经常变为一个模型的主键。这不再是正确的(如果你喜欢你可以在primary_key中手工的传递)。因此,现在在一个模型上拥有多个OneToOneField类型的多个字段是可能的。

3.6 文件间的模型

从另外一个应用中关联一个模型也是OK的。为了达到这个,在你模型定义的最顶上导入这个相关联的模型。然后,只要在需要的其他模型类中引用就可以了。例如:
rom 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,
)


3.7 字段名称限制

Django对于模型字段名称只有2个限制:

字段名称不能是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 自定义字段类型

如果现存的模型字段不能满足你的目的,或者你想要使用不常见的数据库列类型,你可以创建你自己的字段类。<编写自定义模型字段>提供了完整的创建你自己字段的方法。

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)。不需要任何东西,并且给一个模型添加元数据是完全可选的。

<模型选项指引>能找到关于可能的Meta选项的完整列表。

模型属性
对象
一个模型最重要的属性就是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)
在这个例子中最后一个方法是一个属性。

<模型实例指引>有一个完整的关于<自动分配给每个模型的方法>的列表。你可以覆写大部分 -- 阅读<覆写与定义模型方法>,下面 -- 但是有很多你总是想要定义的:
__str__()  (Python 3)
一个Python的“魔法方法”,返回任何对象的一个unicode“展示”。这是Python和Django将会使用的,不管什么时候一个模型实例需要强制作为一个普通的东西显示。最值得注意的是,这发生在当你在一个交互控制台或者管理里显示一个对象时。
你总是想要定义这个方法;默认的完全没有帮助。
__unicode__() (Python 2)
Python 2 等价于__str__()
get_absolute_url()
这告诉Django如何为一个对象计算URL。DJango在自己的管理接口中使用这个,在任何它需要为一个对象指明一个URL的时候。
任何拥有唯一识别它自己的URL的对象都应该定义这个方法。

3.9 覆写与定义模型方法

有另一个模型方法集封装了一些你想要自定义的数据库行为。特别的你将经常想要改变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,你将能确保你的代码将会自动的支持这些参数当它们被添加的时候。

在批量操作中覆写方法不会被调用

请注意当批量删除对象时使用QuerySet或者作为级联删除的结果,对象的delete()方法可能不是必须被调用的。为了确保自定义删除逻辑能够执行,你可以使用pre_delete和/或post_delete符号。

不幸的是,当批量创建或者批量更新时,没有解决方案,因为save(),pre_save,和post_save将不会被调用。

3.10 执行自定义SQL

另一个自定义模式是在模型方法和模型级方法中编写自定义SQL语句。查阅<使用原始SQL>来获得这方面的更多帮助。

4. 模型继承

Django中的模型继承几乎和Python中的类继承一样,但是在页面的开始的基础部分仍然要遵守。那就是说,基类应该用django.db.models.Model来创建子类。

你唯一需要做的决定是是否你想让父母模型成为他们自己的模型(带有他们自己的数据表),或者父类知识普通信息的持有者,这些信息只能通过子模型可见。

在Django中有3中继承方式:

通常,你将想要使用父类持有哪些你不需要在每个子类都重复输入的信息。这个类通常不会单独出现,所以抽象基类是你想要的。
如果你从一个现有模型继承(可能完全来自另一个应用)并且想要每个模型有它自己的数据表,多表继承将会是很好的选择。
最后,如果你想要修改一个模型的Python级行为,而不像修改模型文件,你可以使用代理模型。

4.1 抽象基类

抽象基类是有用的,当你想要将一些普通信息放在许多其他的模型中。你编写你的基类并且将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模型将会有3个字段:name,age和home_group。CommonInfo将不能被用作一个正常的Django模型,因为它是一个抽象基类。它不会产生一个数据表或者有一个manager,并且不会被实例化或者直接保存。

对于许多的用途,这个模型继承的类型将确实是你想要的。它提供了在Python级别的提取公共信息的方法,然而仍然在数据库级别每一个子模型只创建一个数据库。

meta 继承
当一个抽象基类被创建是,DjangoD将任何你声明的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

如果在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的名称的反向名称是common_childa_related方向查询名称是common_chiildas.common.ChildB.m2m字段的反向名称是common_childb_related和反向查询名称是common_childbs。最终,rare.ChildbB.m2m字段的反向名称将是rare_childb_related并且反向查询名称是rare_childbs.如何使用"%(class)s"和"%(app_label)s"部分来组成你的相关名称或相关查询名称取决于你但如果你忘记使用它,Django在你执行系统检查的时候将会抛出错误(或运行migrate)。

如果你没有为抽线基类的一个字段指定related_name属性,默认的反向名称将会是子类的名称后面跟着"_set",就像通常在子类中直接声明一样。例如,在上述代码中,如果related_name属性被省略了,m2m的反向名称在Childa中将会是childa_set,在Childb中将会是childb_set。

在django1.10的修改:
为related_query_name添加了"%(app_label)s"和"%(class)"。

4.2 多表继承

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

如果你有一个同样是Restaurant的Place,你能通过使用模块名称的小写版本来从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声明你自己的OneToOneField。

Meta 和多表继承

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

所以一个子模型不能访问它父类Meta。然而,有几个限制的情况,这种情况下子类从父类继承行为:如果子类没有指定一个ordering属性或get_latest_by属性,它将会从它的父类继承这些。

如果父类有一个ordering并且你不想要子类拥有任何自然的ordering,你可以明确的禁用它:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []


继承和反向关系
因为多表继承使用一个隐式的OneToOneField来链接子类和它的父类,从父节点移到子节点是可能的,如上个例子一样。然而,这使用了ForiegnKey和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').

指定父链接字段
正如提到的,Django将会自动创建一个OneToOneField链接你的子类回到任何非抽象父模型。如果你想要控制属性名称链接回你的父类,你可以创建你自己的OneToOneField并且设置parent_link=True来表名你的字段是返回到父类的链接。

4.3 代理模型

当使用多表继承时,为每个模型的子类创建了一个新的数据表。这通常就是想要的行为,因为子类需要一个地方来存储任何基类中没有的数据字段。有时,然而,你只想改变一个模型的Python行为 -- 也许改变默认的manager,或者添加一个新的方法。

这就是需要代理模型继承的原因:为原始模型创建一个代理。你能创建,删除和修改代理模型的实例并且所有的数据都会保存就好像你在使用源模型(非代理)。不同点是你可以改变像是默认模型排序或者代理的默认manager等事情,而不必修改源模型。

代理模型声明和正常模型一样。通过设置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来排序。

代理模型以常规模型同样的方式继承Meta属性。

QuerySets仍然返回被请求的模型
无论什么时候你查询Person对象,没有方法让Django返回,例如,一个MyPerson对象。Person对象的queryset将会返回这些对象的类型。代理对象的整个点是依赖原始Person的代码将会使用这些并且你自己的代码能使用你包含的扩展(这是其他代码所依赖的)。使用你自己创建的东西来替换所有的Person(或其他任何)模型不是一种方法。

基类限制
一个代理模型应该明确继承自一个非抽象模型类。你不能从多个非抽象模型继承因为代理模型没有在不同的数据的行之间提供任何链接。一个代理模型能从任何数量的抽象模型类中继承,前提是它不定义任何模型字段。一个代理模型同样能从任意数量的共享一个共同的非抽象父类的代理模型中继承。

Django 1.10改变点:
在早期的版本,一个代理模型不能从超过一个共享同一个父类的代理模型中继承。

代理模型管理器
如果你没有在一个代理模型上指定任何模型管理器,它从它的父模型中继承管理器。如果你在代理模型上定义了管理器,它将会变成默认的,尽管在父类上定义的任何管理器也能用。

继续我们上面的例子,当你像这样查询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.db_table你可以创建一个无管理的模型,该模型将会遮蔽现有模型并给它添加Python方法。然而,那将会非常的重复和脆弱因为如果你做了任何修改你需要在2个备份之间保持同步。

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

一般规则是:

如果你正在备份一个现有的模型或者数据表并且不想要所有的源数据表列,使用Meta.managed=False。那个选项通常是用于数据库试图和表,并且不再Django的控制下。
如果你想要修改一个模型的只使用Python的行为,但是保留所有和源模型一样的字段,使用Meta.proxy=True.这设置好了,所以当保存数据时,代理模型是源模型存储结构的一个完整本分。

4.4 多重继承 

就像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


4.5 字段名称 "hiding" 是不允许的

在正常的Python类继承里,是允许子类去覆写父类的任何属性的。在Django,对于模型字段来说,这通常是不允许的。如果一个非抽象模型基类有一个名为author的字段,你将不能创建另一个模型字段或定义一个属性称为author在任何继承自基类的类中。

这个限制不会应用到继承自一个抽象模型的模型字段。这样的字段可以被另一个字段或者值覆写,或者通过使用field_name=None来移除。

Django 1.10的改变:
添加覆写抽象字段的功能。

警告

模型管理器继承自抽象基类。覆写一个被继承Manager引用的继承字段可能引起微妙的bugs。阅读<自定义管理器和模型继承>

注意

一些字段在模型上定义额外的属性,例如,一个ForeignKey使用添加在字段名称后的_id来定义一个额外的属性,对外模型的related_name和related_query_name也是一样。

这些额外的属性不能被覆写除非定义它的字段被改变或者移除所以它不再定义额外属性。

覆写在一个父模型中的字段将导致在区域例如初始化新的实例(指定在Model.__init__被实例化的字段)和序列化上的不同。这些都是正常Python类继承不需要以相同方式处理的特性,所以在Django模型继承和Python的类继承之间的不同不是任意的。

这个限制只应用到Field实例的属性。如果你希望正常Python属性也可以覆写。它同样也只应用到Python看到的属性名:如果你手工指定数据库列名,你可以有相同的列名出现在子类和父类的多表继承的模型中(他们是在2个不同数据表中的列)。

如果你在任何祖先模型中覆写任何模型字段Django将会抛出一个FieldError异常。

4.6 在包内组织模型

manage.py startapp命令创建一个应用结构包含一个models.py文件。如果你有很多的模型,将他们分开组织将会很有用。

为了这样做,创建一个models包。移除models.py并且使用__init__.py创建一个myapp/models/路径和存储你模型的文件。你必须在__init__.py文件中导入模型。

例如,如果你在models路径下有organic.py和synthetic.py:
myapp/models/__init__.py
from .organic import Person
from .synthetic import Robot

明确的导入每个模型而不是使用from .models import * 有一个优势就是不会弄乱名称空间,让代码更可读并保持代码分析工具有用。

另请参见

<模型指引>

涵括所有模型相关的API包括模型字段,相关对象,和查询集。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: