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

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

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


3.2.1 models模型

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

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

3.2.1.1 快速展现:

下面的模型定义了一个“人”,它具备first_name和last_name属性git

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

上面的代码,至关于下面的原生sql语句: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。例如:django

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

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

3.2.1.3 Fields字段

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

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.》ui

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和细节,很长很长的文档.....

相关文章
相关标签/搜索