django1.8 model :Model syntax
Posted 黑猫-警长
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了django1.8 model :Model syntax相关的知识,希望对你有一定的参考价值。
此文翻译自django 1.8.2官方文档
Models
模型是单一的,明确的和你的数据有关的信息的来源.它包含了你存储的数据的基本字段和行为.通常,一个模型对应一个数据库表.
基础知识:
- 每个模型都是django.db.model.Model的子类
- 模型的每个属性都代表一个数据库字段
- 在这2个前提下,django提供了自动生成(automatically-generated)数据库连接(database-access)的API;
Quick example
这个示例模型定义了一个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,是由模型的元数据(metaclass)自动生成的,但可以重写.
- id字段是自动添加的,这个行为(behavior)也可以重写.
- 这段建表SQL语句用的是PostgreSQL语法格式化的.值得注意的是,django根据settings文件里指定的数据库驱动来使用SQL.
Using models
在你定义了模型之后,要告诉django你将要使用这些模型.编辑你的settings文件,更改INSTALLED_APPS设置,添加包含你的models.py的模块名.
INSTALLED_APPS =
# ...
'myapp',
# ...
当你添加新app到INSTALLED_APPS后,确保运行manage.py migrate,也可以先运行manage.py makemigrations.
Fields
模型最重要的部分-并且是唯一必需的部分-是它定义的数据库字段.这些字段由类属性指定.小心选择字段名,不要和模型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)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
Field types
模型里的每个字段都是一个合适的Field类的实例.django使用field class类型来决定一些事情:
- 数据库字段类型(例如 INTEGER, VARCHAR).
- 渲染表单字段时,默认使用的html widget(例如<input type=’text’>,<select>)
- 最基本的验证需求,在django的admin模块和自动生成的表单里有使用.
django有很多内置的字段类型;如果内置的不够用,你可以很容易的定制你自己的字段.
Field options
每个字段都有一些指定字段专用(field-specific)的参数.例如,CharField(和它的子类)需要max_length参数来指定数据库里用来存储数据的VARCHAR的长度.
同样有些通用参数适用于所有字段类型.这些都是可选的.这里是最常用的参数的摘要:
- null
如果是True,django会将空数据当作NULL存入数据库.默认是False.
- blank
如果是True,字段允许是空值(blank).默认是False.
注意这和null不同.null是纯数据库相关的(pure database-related),但是blank是验证相关的(validation-related).如果字段有blank=True,表单验证会允许输入空字符.如果字段是blank=Flase,字段值不能是空字符.
- choices
由2元素元组(2-tuples)组成的可迭代对象,作为字段的choices.如果有这个属性,默认的表单widget就是一个下拉选框而不是标准的文本字段,并限制选择给出的选项.
一个choices list 就像这样:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
每个元组的第一个元素是要存到数据库的值,第二个元素是默认显示在表单widget里或在ModelChoiceField里.给定一个模型对象的实例,choices字段的显示值可以通过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
表单widget里显示的额外的”help”文本.就算你的字段在表单里不使用,也可用于文档 - primary_key
如果为True,这个字段就是模型的主键.
如果你没有在模型里指定任何字段primary_key=True,django会自动的添加一个IntegerFiled作为主键,如果你不需要任何主键,你就得重写默认的主键方法.
主键字段是只读的.如果你更改一个已存在对象的主键的值并保存,这会创建一个新对象.例如:
from django.db import models
class Fruit(models.Model):
name = models.CharField(max_length=100, primary_key=True)
>>> fruit = Fruit.objects.create(name='Apple')
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
['Apple', 'Pear']
- unique
如果为True,这个字段值在表中必须唯一
再次说明,这些只是最常用的字段可选参数的简短描述,全部详情请查看参考文档.
Automatic primary key fields
默认情况下,django会给每个模型一个字段:
id = models.AutoField(primary_key=True)
这是一个自增长主键.
如果你想指定一个自定义的主键,只需要再其中一个字段中指定primary_key=True就行了.如果django看到你显式的设置了Field.primary_key,就不会自动添加id字段了.
每个模型都必须要有一个字段是primary_key=True(显示声明或者自动添加)
Verbose field names
每个字段类型,除了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, verbose_name="the related poll")
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(Place, verbose_name="related place")
约定verbose_name的首字母不需要大写.django会自动将首字母改成大写.
Relationships
显然,关系型数据库的强大在于表关联.django提供提供了定义3中常用的数据库关联关系的方法:多对一,多对多,一对一.
- Many-to-one relationships
使用django.db.models.ForeignKey来定义多对一关系.使用起来就像其他字段类型一样:就像你的模型的类属性一样包含它就行了.
ForeignKey需要一个位置参数:模型的关联类.
例如,如果一个Car模型有一个Manufacturer-就是说一个Manufacturer制造多个汽车,但是每个Car只能由一个Manufacturer-使用下面的定义:
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForergnKey(Manufacturer)
# ...
You can also create recursive relationships (an object with a many-to-one relationship to itself) and relationships to models not yet defined; see the model field reference for details.
建议,但不强制要求,ForeignKey字段(上面的例子是manufacturer)的名字是模型名的小写.当然,你也可以自己取名.例如:
class Car(models.Model):
company_that_makes_it = models.ForeignKey(Manufacturer)
- Many-to-many relationships
使用ManyToManyField来定义多对多关系.就像其他字段类型一样使用就行:模型里包含它就像一个类属性.
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一样, you can also create recursive relationships (an object with a many-to-many relationship to itself) and relationships to models not yet defined; see the model field reference for details.
建议但不强制要求,ManyToManyField的名字(这个例子中是toppings)用关联对象的复数形式.
无论是哪个模型用ManyToManyField都可以,只要其中一个有就行了,不是2个都要有.
通常情况下,ManyToManyField实例放到需要在表单里编辑的对象里.在上面的例子中,toppings在Pizza里(而不是Topping有一个pizzas ManyToManyField)因为一个披萨用多种调料比一种调料可以用于多种披萨更符合自然思维.在上面的的设置中,Pizza表单中用户可以选择多个topping.
ManyToManyField有许多可选参数,详情请看参考文档.
- Extra fields on many-to-many relationships
当你处理简单的多对多关系就像混合,匹配披萨和调料时,标准的ManyToManyField就是你需要的.但是有时候你可能需要处理的数据和2个模型都有关联.例如,假设有一个应用要记录乐手属于哪个组合.这里就有个多对多关系,一个人和这个人参加过的组合,所以你可以使用ManyToManyField来表现这种关系.但是有很多关于成员的细节你要考虑到,比如乐手加入组合的时间.
为了应付这种情况,django允许你定制模型用于多对多关系.你可以在中介模型(intermediate model)里加入额外的字段(extra field).中介模型使用through参数来关联到ManyToManyField,从而指明模型是中介模型(act as an intermediary).就用musician的例子,代码就像下面这样:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # python2使用__unicode__
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # python2使用__unicode__
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
使用中介模型时,显式指定多对多关系模型的外键.这个显式声明定义了2个模型是怎么关联到一起的.
使用中介模型有一些限制:
中介模型必须包含有且只有一个源模型(source model)(这里的例子中是Group)的外键,否则你必须使用ManyToManyField.through_fields来显式指定外键.如果你有多个外键并且through_fields没有指定,会抛出一个validation error.对于目标模型(target model)(这里的例子中是Person),这个规则也适用.
对于一个使用中介模型的有多对多关系的模型来说,允许有2个外键指向同一个模型,但是他们必须当作2个不同的多对多关系处理.If there are more than two foreign keys though, you must also specify through_fields as above, or a validation error will be raised.
When defining a many-to-many relationship from a model to itself, using an intermediary model, you must use symmetrical=False
django1.7更改:
在django1.6及更早版本中,多对多关系的模型每个模型只能有一个外键在中介模型里.
现在你可以像这样来使用
>>> ringo = Person.objects.create(name='Ringo Starr')
>>> paul = Person.objects.create(name='Paul McCartney')
>>> beatles = Group.objects.create(name='The Beatles')
>>> m1 = Membership(person=ringo, group=beatles,date_joined=date(1962, 8, 16), invite_reason='Needed a new drummer.')
>>> m1.save()
>>> beatles.members.all()
[<Person: Ringo Starr>]
>>> ringo.group_set.all()
[<Group: The Beatles>]
>>> m2 = Membership.objects.create(person=paul, group=beatles, date_joined=date(1960, 8, 1), invite_reason='Wanted to form a band.')
>>> beatles.members.all()
[<Person: Ringo Starr>,<Person: Paul McCartney>]
和普通的多对多字段不同,你不能使用add,create或者赋值语句(例如,beatles.members=[…])来创建关联:
# 这个不会起作用
>>> beatles.memebers.add(john)
# 这个也是
>>> beatles.members.create(name='George Harrison')
# 这个也是
>>> beatles.members = [john, paul, ringo, george]
为什么呢,你不能只是新建一个Person和Group的关联-你要指定所有在Membership模型中需要的详情.简单的add,create和赋值调用没有提供一个指定额外细节的方法.
所以,他们对使用中介模型的多对多关系不起作用.唯一的途径是新建中介模型的实例.
remove()方法不起作用,也是这个原因.但是clear()方法可以移除一个实例的所有多对多关系:
>>> # 披头士解散
>>> beatles.members.clear()
>>> # 这将删除中介模型实例
>>> Membership.objects.all()
[]
在通过创建中介模型实例来确立了多对多关系后,你可以开始使用查询了.就像普通的多对多关系一样,你能使用多对多关系模型的属性来查询:
# 找出有成员名字以'Paul'开头的组
>>> Group.objects.filter(members__name__startwith='Paul')
[<Group: The Beatles>]
如果你使用了中介模型,你也可以用他的属性来查询:
# 找出披头士在1 Jan 1961后加入的成员
>>> Person.objects.filter(group__name='The Beatles', membership__date__joined__gt=date(1961,1,1))
[<Person: Ringo Starr>]
如果你要查看membership的信息,你可以直接查询Membership模型:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invit_reason
'Needed a new drummer.'
另一个方法是通过Person对象来查询(多对多反向关系)many-to-many reverse relationship:
>>> 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’库,它有address,phone number等等,如果你另外想建一个restaurants库,没有必要在Restaurant模型里重复这些字段,你可以将Restaurant和Place作一对一关联(因为一个餐馆’是’一个地方,实际上,通常使用继承,它包含了一个隐形的一对一关系).
As with ForeignKey, a recursive relationship can be defined and references to as-yet undefined models can be made; see the model field reference for details.
OneToOneField fields also accept one specific, optional parent_link argument described in the model field reference.
OneToOneField classes used to automatically become the primary key on a model. This is no longer true (although you can manually pass in the primary_key argument if you like). Thus, it’s now possible to have multiple fields of type OneToOneField on a single model.
Models across files
模型可以跨应用关联.为此,需要在定义模型的文件头部import关联模型.然后,在需要的地方关联其他模型就可以了.例如:
from django.db import models
from geography.models import ZipCode
class Restaurant(models.Model):
# ...
zip_code = model.ForeignKey(ZipCode)
Field name restrictions
django只有2个模型字段名限制:
1. 字段名不能是python关键字,因为这会造成python语法错误.例如:
class Example(models.Model):
pass = models.IntegerField() # 'pass'是关键字!
- 字段名不能使用连续多个下划线,因为和django的查询方法的语法冲突.例如:
class Example(models.Model):
foo__bar = models.IntegerField() # 'foo__bar'有2个下划线!
这些限制是可以规避的,因为你的字段名没有必要一定和和数据库字段名相同.详情见db_column可选项.
SQL关键字,例如join,where或select允许作为模型字段名,因为在每个SQL查询中django都对表名和字段名进行了转义.它使用特定的数据库引擎的引号语法.
Custom field types
如果现有的模型字段都不适用或者你想使用一些非常用数据库字段类型,你可以创建你自己的字段类.详情请查看参考文档.
Meta options
使用内部类来给模型添加元数据(metaclass),就像下面的:
from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ['horn_length']
verbose_name_plural = 'oxen'
模型元数据是”不是字段的其他任何东西”(anything that’s not a field),比如说ordering选项(ordering),数据库表名(db_table)或者人类可读的(human-readable)单数和复数形式(verbose_name和verbose_name_plural).Meta类是可选的,非必需.
详情请看参考文档.
Model attributes
objects
模型最重要的属性是Manager.它是提供给django模型的数据库查询接口,用于从数据库中查询实例.如果没有自定义Manager,默认的名称是objects.Manager只能通过模型类来访问,而不是模型实例.
Model methods
通过定义模型方法可以给对象添加”row-level”functionality.不同于Manager方法用于整个表(intended to do ‘table-wide’ things),模型方法可以作用于单个模型实例.
这个技巧将业务逻辑保存到模型里.
例如,这个模型有几个自定义方法:
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):
"Return the person's baby-boomer status"
import datetime
if self.birth_date < datetime.date(1945, 8 ,1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
def _get_full_name(self):
"Returns the person's full name"
return '%s %s' % (self.first_name, self.last_name)
full_name = property(_get_full_name)
例子最后一个方法是property
每个模型都会自动添加一些方法,大部分都可以重写,详情请看参考文档.有几个是常用的:
__str__() (python3)
python3中等价于__unicode__()
__unicode__() (python2)
python的’magic method’,返回一个对象的unicode表示.当一个模型实例需要被显示为普通字符串时python和django会使用这个方法.最常见的,当你使用交互命令行或admin显示一个对象时就会用到.
最好定义这个方法,默认的很不好用.
get_absolute_url()
这表示django怎么为一个对象计算URL.在admin接口或其他任何需要计算对象的URL的时候django会用到这个方法.
任何有唯一标识URL的对象应该定义这个方法.
Overriding perdefined model methods(重写已定义的模型方法)
还有一组模型方法,封装了一些数据库动作,你可以自定义.特别是save()和delete()方法,经常改写.
你可以任意改写这些方法(或其他任何模型方法)来改变数据库行为.
一个重写内置方法的典型案例是当你保存一个对象时想做些什么.例如:
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) # 调用"真的"save()方法
do_something()
你也可以阻止保存
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 == "YoYo Ono's blog":
return # Yoko不能有自己的blog!
else:
super(Blog, self).save(*args, **kwargs)
一定要记得调用父类的方法-super(Blog, self).save(*args, **kwargs)-确保对象保存到数据库里.如果你忘记调用父类的方法,默认的行为将不会发生,数据库不会有变化. 另一点非常重要的是你传递了参数到模型方法*args, **args.django会时不时的添加新参数来扩展内置方法.如果你在方法定义里使用了*args和**kwargs,就能确保当有新参数添加时你的代码能自动支持这些参数.
Overridden model methods are not called on bulk operatins
注意,当使用QuerySet删除大量对象时,delete()方法不一定会被调用.为了确保自定义的删除逻辑会被执行,你可以使用pre_delete()或post_delete()信号.
不幸的是,当大量create或update对象时没有变通方法.Unfortunately, there isn’t a workaround when creating or updating objects in bulk, since none of save(), pre_save, and post_save are called.
Executing custom SQL
另外一个常用的模式是在模型方法和模块级方法里写自定义的SQL语句.详情请看参考文档.
Model inheritance
django的模型继承的工作方式和正常的Python类继承几乎一样,但是还有有些基本原则要遵守的.基类一定是django.db.models.Model.
你唯一要做的决定是父模型是否有属于自己的模型的权利(有自己的数据库表),或者说父类只保留公共信息,只有通过子模型才有可见性(only be visible throuth the child models).
django有3中继承方式.
1. 通常,你只会想使用父类来保存你不想要键入每个子模型的信息.这种类几乎不会单独使用,那么抽象基类(Abstract base class)就是你要的.
2. 如果你继承了一个已存在的模型(也许是另外一个应用),并希望每个模型都有自己的数据库表,就使用多表继承(Multi-table inheritance).
3. 最后,如果你只想修改一个模型的Python-level behavior,而不改变模型的字段,你可以使用代理模型(Proxy models).
Abstract base classes
当你想把一些公共信息保存到另外的模型时抽象基类很有用.写一个基类并在Meta类里添加abstract=True.这个模型不会在数据库里建任何表.当它用于作其他模型的基类时,它的字段会被添加到子类里.如果父类有字段名和子类相同,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,不能实例化,不能直接保存(saved directly).
你可以使用这种形式的模型继承应对大多是情况.它提供了一个方法在python层面来抽取公共信息,数据库层面一个子类一个表.
- Meta inheritance
新建一个抽象基类时,django会将在基类里声明的内部类Meta当作一个属性.如果子类没有声明自己的Meta类,就会继承父类的Meta.如果子类想扩展父类的Meta类,就继承它,例如:
from django.db import Model
class CommonInfo(models.Model):
# ...
class Meta:
abstract = True
ordering= ['name']
class Student(CommonInfo):
# ...
class Meta(CommonInfo.Meta):
db_table = 'student_info'
django会对抽象基类的Meta类有一个调节(adjustment):在使用Meta属性前,设置abstract=True.这就是说抽象基类的子类不会自动变成抽象类.当然,你也可以是一个抽象基类继承自另一个抽象基类.你只要记得显式的设置abstract=True就行了.
有些属性出现在抽象基类的Meta类里就没有意义了.例如,其中有db_table就表示子类(没有定义自己的Meta)将会使用同样的数据库表,这肯定不是你想要的.
- Be careful with related_name
如果你在ForeignKey或ManyToManyField上使用了related_name属性,你必须为字段指定一个唯一的反向名称(unique reverse name).这就会给抽象基类造成一个问题,因为类的字段会出现在每一个子类中,而且值相同.
为了解决这个问题,你在抽象基类中使用related_name时,名称部分应该要包含”%(app_label)s”和”%(class)s”.
- ’%(class)s’指字段所在的子类的lower-cased name.
- ‘%(app_label)s’指子类所在的应用的lower-cased name.每个应用名都必须是唯一的,应用内的模型类名称在应用内也必须是唯一的,所以最后的名称都是不相 同的.
例如,common/models.py:
from django.db import models
class Base(models.Model):
m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")
class Meta:
abstract = True
class ChildA(Base):
pass
class ChildB(Base):
pass
另一个app rare/models.py:
from common.models import Base
class ChildB(Base):
pass
common.ChildA.m2m字段的反向名称(reverse name)是common_childa_related,同时common.ChildB.m2m字段的反向名称时common_childb_related,最后rare.ChildB.m2m字段的反向名称时rare_childb_related.这取决于你怎么使用’%(class)s’和’%(app_label)s’来组成你的关联名称,但是如果你忘记使用,在使用系统检查(或运行migrate)时django会抛出异常.
如果你没有在抽象基类里为字段指定related_name属性,默认的反向名称将是子类的名称跟着’_set’,就像是你直接在子类里声明变量时正常生成的那样.例如,在上面的代码中,如果related_name属性省略了,m2m字段的反向名称在ChildA里是childa_set,在ChildB里是childb_set
Multi-table inheritance
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)
# 如果p是一个Restaurant对象,就会得到子类:
>>> p.restaurant
<Restaurant...>
但是,如果上面例子中的p不是一个Restaurant(它是直接创建的Place对象或是其他类的父类),p.restaurant就会抛出Restaurant.DoesNotExist异常.
- Meta and multi-table inheritance
在多表继承中,子类继承父类的Meta是没有意义的.All the Meta options have already been applied to the parent class and applying them again would normally only lead to contradictory behavior (this is in contrast with the abstract base class case, where the base class doesn’t exist in its own right).
所以,子模型不访问父模型的Meta类.但是也有例外:如果子类没有指定ordering属性或get_lasted_by属性,子类就会继承父类的.
如果父类有ordering属性,而你不想子类有自然排序,你可以显示指定它无效:
class ChildModel(ParentModel):
# ...
class Meta:
# 移除父类的ordering影响
ordering = []
- Inheritance and reverse relations
因为多表继承使用了隐式的OneToOneField来连接子类和父类,it’s possible to move from the parent down to the child,在上面的例子中.然而它使用的名称related_name默认是给ForeignKey和ManyToManyField关系用的.如果你想使用,就必须指定每个字段的related_name.如果你忘记了,django会抛出validate error.
例如,还是使用上面的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,或添加一个新方法.
这就是代理模型要做的:为原始模型创建一个代理.你可以创建,删除和更新代理模型的实例并且所有的数据会像原始模型一样保存.不同之处在于你可以在代理模型中更改一些东西,比如默认的ordering或默认的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>
你也可以使用代理模型来定义不同的ordering.你可能不会将Person模型排序,但使用代理时可以更具last_name属性排序.这很容易:
class OrderedPerson(Person):
class Meta:
ordering = ['last_name']
proxy = True
现在普通的Person查询不会排序,OrderedPerson查询会根据last_name排序.
QuerySets still return the model that was required
django没办法使Person查询返回MyPerson对象.查询Person还是返回Person类型的对象.The whole point of proxy objects is that code relying on the original Person will use those and your own code can use the extensions you included (that no other code is relying on anyway). It is not a way to replace the Person (or any other) model everywhere with something of your own creation.Base class restrictions
一个代理模型必须继承自一个非抽象类,不能继承自多个非抽象类.一个代理模型能继承多个抽象模型类,如果他们没有定义任何模型字段.Proxy model managers
如果你没有在代理模型里指定manager,它就会继承父模型的manager.如果你定义了manager,它就会变成末日呢的,虽然父类里定义的manager仍然可用.
继续上面的例子,你可以查询Person模型时可以改变默认的manager:
from django.db import models
class NewManger(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
如果你想给代理模型添加新的manager,而不代替默认的,你可以c使用ustom manager文档里介绍的技巧:新建一个基类包含新的manager,然后继承它.
# 为新manager创建一个抽象类.
class ExtraManagers(models.Model):
secondary = NewManger()
class Meta:
abstract = True
class MyPerson(Person, ExtraMangers):
class Meta:
proxy = True
你可能很少需要这样用,但是当你需要时,这就很必要了.
- Differences between proxy inheritance and unmanaged models
代理模型看起来很像是创建一个unmanaged模型,在模型的Meta类里使用managed属性.2种方案不一样,你得仔细考虑要使用哪一种.
一个不同点是你可以(实际上是必须,除非你想要一个空模型)在Meta.managed=False的模型里指定模型字段.You could, with careful setting of Meta.db_table create an unmanaged model that shadowed an existing model and add Python methods to it. However, that would be very repetitive and fragile as you need to keep both copies synchronized if you make any changes.
另一个对代理模型很重要的不同点是模型manager怎么处理.代理模型设计成就像它代理的模型一样运作.所以他们继承了父模型的manager,包含了默认的manager. In the normal multi-table model inheritance case, children do not inherit managers from their parents as the custom managers aren’t always appropriate when extra fields are involved.
When these two features were implemented, attempts were made to squash them into a single option. It turned out that interactions with inheritance, in general, and managers, in particular, made the API very complicated and potentially difficult to understand and use. It turned out that two options were needed in any case, so the current separation arose.(这段话的意思是,反正情况很复杂)
所以,通用规则是:
- 如果你想复制一个已有的模型或数据库表,但不是想要所有表字段,就使用Meta.managed=False.这个选项用于模型视图并且表不收django控制.
- 如果你想改变一个模型的python-only方法,但要保留和原始模型一样的的所有字段,使用Meta.proxy=True.这样的话,当数据保存时,代理模型就是一个原始模型的存储结构的拷贝.
Multiple inheritance
就像python的子类一样,django模型有可能继承自多个父模型.Keep in mind that normal Python name resolution rules apply.特殊名称(例如Meta)出现的第一个基类将会被使用;例如,如果多个父类都有Meta类,只有第一个会被使用,其他的会忽略.
通常,没有必要使用多继承.主要使用”mix-in”类:adding a particular extra field or method to every class that inherits the mix-in.尽量使你的继承关系简单明了,这样你就不会被数据到底是哪来的搞的头大了.
django1.7更改
在django1.7之前,继承自多个模型,并有一个id主键字段不会抛出异常,但是会导致数据丢失.例如,看看这些模型(因为冲突的id字段所以不再验证):
class Article(models.Model):
headline = models.CharField(max_length=50)
body = models.TextField()
class Book(models.Model):
title = models.CharField(max_length=50)
class BookReview(Book, Article):
pass
这个代码片段展示了怎么创建一个子对象,重写先前创建的父对象的值:
>>> article = Article.objects.create(headline='Some piece of news.')
>>> review = BookReview.objects.create(headline='Review of Little Red Riding Hood.', title='Little Red Riding Hood')
>>>
>>> assert Article.objects.get(pk=article.pk).headline == article.headline
Traceback (most recent call last):
File "<console>", line 1, in <module>
AssertionError
>>> # "Some piece of news."headline被重写了.
>>> Article.objects.get(pk=article.pk).headline
'Review of Little Red Riding Hood.'
为了正常使用多继承,你可以在基类中使用显式的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(Or use a common ancestor to hold the AutoField):
class Piece(models.Model):
pass
class Article(Piece):
...
class Book(Piece):
...
class BookReview(Book, Article):
...
Field name “hiding” is not permitted
在普通的python类继承中,子类可以重写父类的任何属性.在django里,重写字段实例属性是不允许的(至少目前不允许).如果基类有个字段叫author,你不能在子类中创建一个字段叫author.
重写父类的字段涉及到很复杂的问题,比如初始化新实例(在Model.init里指定实例化哪个字段)和序列化.这些特性不能用普通的python类继承来处理,所以django模型继承和python类继承有很大的不同.
这个限制只针对于字段实例属性.普通的python属性可以被重写,如果你愿意. It also only applies to the name of the attribute as Python sees it:如果你手动指定了数据库字段名,在多表继承中,你能在子类和父类中看到相同的字段名出现(在2个不同的数据库表中的字段).
如果你重写父模型中的模型字段,django会抛出FieldError异常.
以上是关于django1.8 model :Model syntax的主要内容,如果未能解决你的问题,请参考以下文章
无法在 django 1.8.4 中使用 GenericForeignKey