Django 1.10 中文文档------3.2.1 模型Models

欢迎大家访问我的个人网站《刘江的博客和教程》:www.liujiangblog.com

主要分享Python 及Django教程以及相关的博客


3.2.1 models模型

通常一个模型映射一张单独的数据表。
基本概念:

  • 每个model都是django.db.models.Model的子类
  • model的每个属性代表数据表的某一列
  • Django将自动为你生成数据库访问API

3.2.1.1 快速展示:

下面的模型定义了一个“人”,它具有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)

上面的代码,相当于下面的原生sql语句:

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

注意:

  • 表名myapp_person由Django自动生成:项目名称+下划线+小写类名。你可以重写这部分功能的机制。
  • Django自动创建id自增主键,当然,你也可以自己指定主键,
  • 上面的sql语句基于PostgreSQL 语法,可能与你的实际情况有差别。

3.2.1.2 使用模型

创建了model之后,在使用它之前,你需要先在settings文件中的INSTALLED_APPS 处,注册models.py文件所在的APP。例如:

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

当你每次在INSTALLED_APPS处增加新的APP时,请务必执行命令python manage.py migrate。有可能要先make migtrations。

3.2.1.3 Fields字段

model中最重要也是必须定义的部分。请不要使用clean、save、delete等model API内置的名字,防止命名冲突。
范例:

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()
Fields types字段类型

fileds类型的作用:

  • 决定数据库中对应列的数据类型
  • HTML中对应的form field类型,例如<input type=“text” />
  • 在admin后台和自动生成的form表单中最小的数据验证需求

    3.22节《Making queries》
    6.15节《model field reference》《Field API reference》
    4.3节《Writing custom model fields.》

AutoField
一个自动增加的整数类型。通常你不需要使用它,Django自动帮你添加下面的字段:
id = models.AutoField(primary_key=True)

BigAutoField
(1.10新增)
64位整数,类似AutoField。1 to 9223372036854775807

BigIntegerField
64位整数,类似IntegerField ,-9223372036854775808 to 9223372036854775807。
在默认的form类型是textinput(用django的form功能自动生成的input标签)。

BinaryField
二进制数据类型。使用受限,慎用。

BooleanField
布尔值类型。默认值是None。
默认的form类型是input checkbox。
如果要接收null值,请使用NullBooleanField。

CharField
字符串类型。必须接收一个max_length参数。
默认的form类型是input text。
最常用的filed!

CommaSeparatedIntegerField
逗号分隔的整数类型。必须接收一个max_length参数。
常用于表示较大的金额数目,例如1,000,000元。

DateField
class DateField(auto_now=False, auto_now_add=False, **options)
日期类型。
一个Python中的datetime.date的实例。
在form中的默认类型是text。在admin后台,Django会帮你自动添加一个js的日历表和一个“Today”快捷方式,以及附加的日期合法性验证。
参数:(所有参数互斥,不能共存)
auto_now:每当对象被保存时将字段设为当前日期,常用于保存最后修改时间。
注意,只有在使用save()方法时才更新,其它操作不更新。
auto_now_add:每当对象被创建时,设为当前日期,常用于保存创建日期。
注意,它是不可修改的。

设置上面两个参数就相当于给field添加了editable=False and blank=True属性。如果想具有修改属性,请用default参数:
对于 DateField: default=date.today - from datetime.date.today()
对于 DateTimeField: default=timezone.now - from django.utils.timezone.now()

DateTimeField
class DateTimeField(auto_now=False, auto_now_add=False, **options)
日期时间类型。
Python的datetime.datetime的实例。与DateField相比就是多了小时、分和秒的显示,其它功能、参数、用法、默认值等等都一样。

DecimalField
class DecimalField(max_digits=None, decimal_places=None, **options)
固定精度的十进制小数。
相当于Python的Decimal实例,必须提供两个指定的参数!
参数:
max_digits:最大的位数,必须大于或等于小数点位数
decimal_places:小数点位数,精度。
范例:储存最大不超过999,带有2位小数位精度的数,定义如下:
models.DecimalField(..., max_digits=5, decimal_places=2)
5来自3+2!

当 localize=False,它在form中默认为NumberInput 类型。否则,是text类型。

DurationField
持续时间类型
存储一定期间的时间长度。类似python中的timedelta。在不同的数据库实现中有不同的表示方法。常用于进行时间之间的加减运算。但是小心了,这里有坑,PostgreSQL等数据库之间有兼容性问题!

EmailField
class EmailField(max_length=254, **options)
邮箱类型。
使用EmailValidator进行合法性验证。

FileField
class FileField(upload_to=None, max_length=100, **options)
上传文件类型

filed类型表
自定义filed类型

Field options选项

model field reference

字段选项

详细看6.15节《field option》
除了类似max_length是对CharFiled的必须参数外。还有一些是可选的常用参数:

  • null:True时,Django在数据库用NULL保存空值。默认False。
  • blank:true时,字段可以为空。默认false。和null不同的是,null是纯数据库层面的,而blank是验证相关的,它与表单验证是否允许输入框内为空有关,于数据库无关。所以要小心一个null为false,blank为true的字段接收到一个空值可能会出bug或异常。
  • choices:用于选择框,需要先提供一个二维的二元元组,第一个元素表示存在数据库内真实的值,第二个表示页面上显示的具体内容。例如:

    YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
    )
    要显示一个choices的值,可以使用get_FOO_display()方法,其中的FOO用字段名代替。

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:字段的默认值,可以是值或者一个回调对象。每次创建新对象时该回调都会被执行。注意:在某种原因不明的情况下将default设置为NONE,可能会引发intergyerror:not null constraint failed,即非空约束失败异常,导致python manage.py migrate失败,此时可将None改为False或其它的值,只要不是None就行。
  • help_text:额外的帮助文本显示在表单部件上。
  • primary_key:主键。设置为True时,当前字段变为主键,并关闭Django自动生成的id主键功能。另外,主键字段不可修改,如果你赋个新值则会创建个新记录。

    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时,在整个表内该字段的数据不可重复。

自动主键字段

默认情况下,Django给你提供了自动的主键:
id = models.AutoField(primary_key=True)

字段详细名称

除了外键、多对多和一对一字段外,所有的字段都可以有一个可选的第一位置参数:verbose name。如果没给这个参数,Django会利用字段的属性名自动创建它,并将下划线转换为空格。
下面这个例子verbose name是"person’s first name":
first_name = models.CharField("person's first name", max_length=30)
下面这个例子verbose name是"first name":
first_name = models.CharField(max_length=30)

对于外键、多对多和一对一字字段,由于第一个参数需要用来指定模型类,因此必须用关键字参数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",
)
关系relationships
  1. 多对一:也就是外键,使用django.db.models.ForeignKey。需要第一位置参数 为相关联的模型类。例如:
from django.db import models
class Manufacturer(models.Model):
    # ...
    pass
    
class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    # ...

外键关系字段放在多的一方,比如上面的多种汽车出自同一厂商。
也可以创建递归关系(自己多对一关联自己,使用self作为指向的模型名),或者关联到尚未创建的模型。
建议将外键字段名设置为关联类的小写名称。非强制。
更多ForeignKey相关,请查看6.15节《the model field reference》

  1. 多对多:ManyToManyField。需要一个位置参数,指向关联的模型。例如:
from django.db import models

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

同一对一一样,也可以建立递归的自己关联自己的多对对,或关联到一个尚未定义的模型。
建议将字段名取成关联模型的小写复数,例如toppings。
多对多字段放在关联双方的任何一方都可以,但是只能在一方,不能同时。但通常会考虑现实的逻辑,将其放在更符合基本情况的一方。
更多ManyToManyField相关,请查看6.15节《the model field reference》

  1. 多对多的额外字段
    一般情况,普通的多对多已经够用,无需自己创建第三张关系表。但是某些情况可能更复杂一点,比如有音乐家表和音乐家分组表,这是个多对多的关系,但是如果你想保存某个音乐家加入某个音乐家分组的时间呢?
    Ddjango提供了一个through参数,用于指定中间模型,你可以将类似加入时间等其他字段放在这个中间模型内。例子如下:
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)

对于中间模型,有一些限制:

  • 中间模型只能包含一个指向源模型的外键关系,上面例子中,也就是在Membership中只能有person和group外键关系各一个,不能多。否则,你必须显式的通过ManyToManyField.through_fields指定关联的对象。参考下面的例子:
from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=50)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
    Person,
    through='Membership',
    through_fields=('group', 'person'),
    )

class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(
    Person,
    on_delete=models.CASCADE,
    related_name="membership_invites",
    )
    invite_reason = models.CharField(max_length=64)
  • 对于一个通过中间模型关联自身的多对多,两个同样的外键关系是允许的,但此时他们被看做不同的两边。但是,如果多于2个,一样要如同上面显式的通过ManyToManyField.through_fields指定关联的对象。
  • 当你通过中间模型创建一个关联自身的多对多,你必须显式的指出symmetrical=False。这样,Django才会关闭默认的对称特性。具体参考6.15

下面是一些使用例子:

>>> 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(),remove(), 和set()方法来创建、删除关系,看下面:

>>> # 无效
>>> beatles.members.add(john)
>>> # 无效
>>> beatles.members.create(name="George Harrison")
>>> # 无效
>>> beatles.members.set([john, paul, ringo, george])

为什么?因为上面的方法无法提供加入时间、邀请原因等中间模型需要的字段内容。唯一的办法只能是通过创建中间模型的实例来创建这种类型的多对多关联。
但是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]>

可以像普通模型一样使用中间模型:

>>> 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.'
>>> 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.'
  1. 一对一关系:OneToOneField,同样需要关联模型作为第一位置参数。

举例,你设计一个“场所”的模型,包括地址、电话等等。然后,你又想创建一个基于“场所”的餐馆模型,你不需要重复上面的场所的字段,只需要在餐馆模型中建立一个OneToOneField关联到“场所”模型。(事实上,通常我们会使用继承的方法,它隐含了一个一对一关系)。
同样,一对一也可以递归关联自己,或关联未定义模型。
一对一还有一个可选的parent_link参数。

其它模块的Models

直接在文件顶部导入其它模块内的模型,然后正常使用!

from django.db import models
from geography.models import ZipCode

class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(
        ZipCode,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
field name的命名限制
  • 不能是Python的保留关键字
  • 不能包含2个及以上的下划线。因为2个下划线在Django查询语法中有特殊作用。

但是SQL的保留字,如join、where和select是可以用的。

自定义字段类型

参考4.3《Writing custom model fields》

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

metadata:指的是“任何非字段相关的内容”,例如排序依据(ordering),表名(db_table),或者人类可读的单数、复数名((verbose_name and verbose_name_plural)。所有的都是可选,非必须的。完整的参考表见6.15.6《model option reference》

3.2.1.5 模型属性

objects:对于一个模型,最重要的属性是Manager(管理器)。它是模型用来查询、操作数据的接口。如果没有自定义Manager,那么它的默认名字就是“objects”。它只能通过类名进行访问,不能通过类的实例访问。

3.2.1.6 模型方法

下面是在模型中自定义方法的实例:

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"
        
    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) # 将方法转换为属性

下面是2个常用的经常被定义的方法:

str():python3版本,在打印模型时,指定显示的内容。
get_absolute_url():Django用它来获取对象的URL,每一个包含URL的对象都必须定义这个方法。参考6.15.

重写内置的模型方法

例如,你想在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()
        # 调用内置的save()方法
        super(Blog, self).save(*args, **kwargs) 
        do_something_else()

或者阻止某些人无法进行save()。

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()
    
    def save(self, *args, **kwargs):
        if self.name == "Yoko Ono's blog":
            # 叫这个名字的博客无法保存
            return
        else:
            # 其它的正常保存
            super(Blog, self).save(*args, **kwargs)

注意三点:

  1. 一定要调用父类方法super(Blog, self).save(*args,
    **kwargs)保证正常的工作
  2. 一定要用*args,**kwargs的传参方式,保证无论如何,参数都被正确的传递给父类方法。
  3. 在进行批量处理时,自定义方法可能不管用。

3.2.1.7 模型的继承

所有的模型类必须继承django.db.models.Model。
Django有三种继承的方式:

  • 抽象类:被用来继承的类,Abstract base classes,将共同的数据抽离出来,供子类继承重用,它不会创建表
  • 多表类:Multi-table inheritance,每一个模型都有自己的数据库表。
  • 代理模型:Proxy models
继承抽象基类:

值需要在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模型将有name,age,home_group三个字段。基类和子类不能有一样的字段名。

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'

抽象基类也可以继承别的抽象基类,但不要忘记在Meta类里添加abstract=True。这个选项才是决定一个类是普通的类还是抽象基类的根本。

区别related_name和related_query_name,当你在抽象基类中使用时应该包含’%(app_label)s’和’%(class)s’:

  • ’%(class)s’用字段所属子类的小写名替换
  • ’%(app_label)s’用子类所属app的小写名替换

例如,对于common/models.py模块:

from django.db import models

class Base(models.Model):
    m2m = models.ManyToManyField(
    OtherModel,
    related_name="%(app_label)s_%(class)s_related",
    related_query_name="%(app_label)s_%(class)ss",
    )
    
    class Meta:
        abstract = True
        
class ChildA(Base):
    pass

class ChildB(Base):
    pass

对于另外一个模块rare/models.py:

from common.models import Base

class ChildB(Base):
    pass

对于上面的继承关系:

  • common.ChildA.m2m字段的reverse name(反向关系名)应该是common_childa_related;reverse query name(反向查询名)应该是common_childas。
  • common.ChildB.m2m字段的reverse name(反向关系名)应该是common_childb_related;reverse query name(反向查询名)应该是common_childbs。
  • rare.ChildB.m2m字段的reverse name(反向关系名)应该是rare_childb_related;reverse query name(反向查询名)应该是rare_childbs。

具体时候什么名字,取决你如何通过‘%(class)s’‘ and ’%(app_label)s构造名称字符串。但是,如果你忘了这个技术细节,那么在使用时会弹出异常。

multi-table inheritance多表继承

这种继承方式,父类和子类都有自己的数据库表,内部隐含了一个一对一的关系。例如:

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)

Restaurant类将包含Place类的字段,并且各有各的数据库表和字段,比如:

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

如果一个Place同时也是一个Restaurant,你可以使用小写的子类名,在父类中访问它,例如:

>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>

但是,如果这个Place是个纯粹的Place,并不是一个Restaurant,那么上面的调用方式会报错Restaurant.DoesNotExist。

Meta和多表继承

在多表继承的情况下,由于父类和子类都在数据库内有物理存在的表,父类的Meta类会对子类造成不好的影响,因此,Django在这种情况下关闭了子类继承父类的Meta功能。这一点和抽象基类的继承方式有所不同。
但是,还有2个meta元素特殊一点,那就是ordering和get_latest_by,这两个参数是会被继承的。因此,如果在多表继承中,你不想让你的子类继承父类的上面两种参数,就必须在子类中显示的指出或重写。如下:

class ChildModel(ParentModel):
    # ...
    
class Meta:
    # 移除父类对子类的排序影响
    ordering = []

继承和反向关联

因为多表继承使用了一个隐含的OneToOneField来链接子类与父类,所以象上例那样,你可以用父类来指代子类。但是这个OnetoOneField字段默认的related_name值与ForeignKey和 ManyToManyField默认的反向名称相同。如果你与该父类的另一个子类做多对一或是多对多关系,你就必须在每个多对一和多对多字段上强制指定related_name。如果你没这么做,Django就会在你运行或验证(validation)时抛出异常。

仍以上面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')。

指定链接父类的字段

之前提到,Django会自动创建一个OneToOneField字段将子类链接至非抽象的父model 。如果你想指定链接父类的属性名称,你可以创建你自己的OneToOneField字段并设置 parent_link=True,从而使用该字段链接父类。

Proxy models代理模型

使用多表继承时,父类的每个子类都会创建一张新数据表,通常情况下,这正是我们想要的操作。这是因为子类需要一个空间来存储不包含在基类中的字段数据。但有时,你可能只想更改model在Python层的行为。比如:更改默认的manager,或是添加一个新方法。

代理模型可以实现这一点:为原始模型创建一个代理 。你可以创建,删除,更新代理model 的实例,而且所有的数据都可以像使用原始model一样被保存。不同之处在于:你可以在代理model中改变默认的排序设置和默认的manager,而不会对原始model产生影响。

声明代理模型和声明普通模型没有什么不同。设置Meta类中proxy的值为True,就完成了对代理模型的声明。
举例,假设你想给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>

下面是通过代理实现排序,但原父类不排序的方法:

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

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

查询集始终返回请求的模型
也就是说,没有办法让django在查询Person对象时返回MyPerson对象。Person对象的查询集会返回相同类型的对象。代理对象的要点是:它会使用依赖于原生Person的代码,而你可以使用你添加进来的扩展对象(它不会依赖其它任何代码)。而并不是将Person模型(或者其它)在所有地方替换为其它你自己创建的模型。

基类的限制

  • 代理模型必须继承自一个非抽象基类。并且不能继承自多个非抽象基类,这是因为一个代理模型不能连接不同的数据表。
  • 代理模型可以同时继承任意多个抽象基类,前提是这些抽象基类没有定义任何模型字段。
  • 代理模型可以同时继承多个别的代理模型,前提是这些代理模型继承同一个非抽象基类。(早期Django版本不支持这一条)

代理模型管理器
如不指定,则继承父类的管理器。如果你自己定义了管理器,那它就会成为默认管理器,但是父类的管理器依然有效。如下例子:

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,这是因为创建的非托管模型映射某个已存在的模型,并且有自己的方法。如果你要保证这两个模型同步并对程序进行改动,那么就会变得繁冗而脆弱。
一般规则是:

  • 如果你要借鉴一个已有的模型或数据表,且不想涉及所有的原始数据表的列,那就使用 Meta.managed=False。通常情况下,对模型数据库创建视图和表格不需要由Django控制时,就使用这个选项。
  • 如果你想对模型做Python层级的改动,又想保留字段不变,那就令Meta.proxy=True。因此在数据保存时,代理模型相当于完全复制了原始模型的存储结构。
多重继承

注意,多重继承和多表继承是两码事,两个概念。

Django的模型体系支持多重继承,就像Python一样。同时,一般的Python名称解析规则也会适用。出现特定名称的第一个基类(比如Meta)是所使用的那个,这意味着如果多个父类都含有 Meta类,只有第一个父类的会被使用,剩下的会忽略掉。

通常,你不需要使用多重继承。最常用的情况是“混入”(mix-in):为每一个继承了minx-in的类添加一个特别额外的字段或方法。
但是,尽量让你的继承关系简单和直接,避免不必要的混乱和复杂。

请注意,继承同时含有相同id主键域的类将抛出异常。为了解决这个问题,你可以在基类模型中显式的使用AutoField字段。如下例子所示:

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

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

class BookReview(Book, Article):
    pass

或者使用一个共同的祖先来持有AutoField字段,如下所示:

class Piece(models.Model):
    pass
    
class Article(Piece):
    ...
    
class Book(Piece):
    ...
    
class BookReview(Book, Article):
    pass
重写字段

在python中,子类可以重写所有的父类属性。但在Django中却不一定。如果一个非抽象模型基类有一个叫做author的字段,那么在它的子类中,你不能创建任何也叫做author的模型字段或属性(这个限制对抽象模型无效)。这些字段有可能被另外的字段或值重写,或通过设置filed_name=None而被移除。

警告:模型管理器继承自抽象基类。重写一个被继承管理器引用的继承字段可能导致小bug。参考3.2《Custom managers and model inheritance》

注意:一些字段会在模型上定义一些额外的属性,例如ForeighKey会定义一个额外的属性(将"_id"附加在字段名上)。就像related_name和related_query_name一样。这些额外的属性不可被重写,除非定义他们的字段被改变或移除了。

重写父类的字段会导致很多麻烦,比如:初始化实例(指定在Model.__init__中被实例化的字段)和序列化。而普通的Python类继承机制并不能处理好这些特性。所以Django的继承机制被设计成与Python有所不同,这样做并不是随意而为的。

这些限制仅仅针对做为属性使用的Field实例,并不是针对Python属性,Python属性仍是可以被重写的。 在 ython看来,上面的限制仅仅针对字段实例的名称:如果你手动指定了数据库的列名称,那么在多重继承中,你就可以在子类和某个祖先类当中使用同一个列名称。(因为它们使用的是两个不同数据表的字段)。

如果你在任何一个祖先类中重写了某个 model 字段,Django 都会抛出 FieldError异常。

3.2.1.8 在包中组织模型

manage.py startapp命令的执行会创建一个app的文件结构,并包含一个models.py文件。但是,如果你有很多模型,那么将它们分隔放在不同的文件中会是个好主意。

想这么做,只需要创建一个模型包。首先,移除models.py文件,再建立一个myapp/models/目录,在目录里创建个__init__.py文件,最后创建存放模型的文件。你必须在__init__.py中导入那些模型models。
例如,如果你有一个organic.py和synthetic.py文件在models目录里,那么,init.py文件里应该这么写代码:
myapp/models/init.py

from .organic import Person
from .synthetic import Robot

在文件中显式的导入每一个模型比使用from .models import * 这种一股脑的导入方式更好,这样不会导致命名空间的混乱,让代码更可读,更利于代码分析工具进行检查。

更多请查看6.15节的Models,这里包含了所有的API和细节,很长很长的文档.....

posted @ 2016-10-18 19:18 大江东流去 阅读(...) 评论(...) 编辑 收藏

作者:刘江

版权所有,出自www.cnblogs.com/feixuelove1009