抽象基类在你将一些共同信息导入到多个其他模型的时候很有用。
你写你的基类,并在Meta类(元类)中设置abstract=True。该模型不用于生成任何数据库表。反而,当抽象基类用于其他模型的一个基类是,它的fields(字段)会被添加到那些子类中。
这个是错误的:抽象基类和其子类拥有同名的字段(Django会报异常)。
一个例子:
from django.db import models
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模型,因为这是个抽象基类。CommonInfo不会生成一个数据库表或者拥有一个管理器,不能被实例化和直接存储。
对于许多用户来说,这种继承模型类型正是你想要的。它提供了在Python级别分解共同信息的一种方法,同时只根据数据库等级的子模型来生成一个数据库表。
Meta继承:
创建抽象基类的时候,Django 会将你在基类中所声明的有效的 Meta 内嵌类做为一个属性。
如果子类没有声明自己的Meta类,它将继承父类的Meta类。如果子类想要扩展父Meta类,可以子类化。
例子:
from django.db import models
class CommonInfo(model.Model):
#...
class Meta:
abstrasct = 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或者MangToManyField中正使用related_name或者related_query_name时,你总要给字段(field)明确一个unique reverse name(唯一反向名称)和query time。这通常会在抽象基类中导致出现问题,因为Django会将基类字段添加进子类当中,而每个子类的字段属性值基本相同。这样,在使用ForeignKey或者ManyToManyField反向指定时,就无法确定指向哪个子类了。
为解决这个问题,当你(仅)在抽象基类中使用related_time或者related_query_name时,部分值应该包含'%(app_label)s'和‘%(class)s’。
‘%(class)s’会被子类名字取代;
‘%(app_label)s’会被子类所在App的名字取代。
举例:
假定有个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
在另一个app,rare/models.py中:
from common.models import Base
class ChildB(base):
pass
那么,common.ChildA.m2m字段的反向名称common_childa_related,
common_ChildB.m2m字段的反向名称为commom_childb_related;
rare app中,rare.ChildB.m2m字段的反向名称为rare_childb_related.
如果你没有在抽象基类中为某个关联字段定义related_name属性,那么默认的反向名称就是子类名称加上‘_set’,它能否正常工作取决于你是否在子类中定义了同名字段。
例如,上面代码中,如果去掉related_name属性,在ChildA中,m2m字段的反向名称就是childa_set;
而ChildB的m2m字段的反向名称就是childb_set.
Multi-table继承 多表继承
这是Django支持的第二种继承方式。使用这种继承方式时,同一层级下的每个子model本身是一个model。
每个子model都有自己的数据库表,可单独查询与创建。继承关系在子model和它的每个父类之间采用链接方式。(通过自动创建的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,你可以使用小写的model名称,从Place对象得到Restaurant对象:
>>>p = Place.objects.get(id=12)
#If p is restaurant object, this will give the child class:
>>>p.restaurant
<Restaurant:...>
如果上例中p不是一个Restaurant,(它已经直接有Place对象创建或者是一些其他类的父类)。引用p.restaurtant将抛出Restaurant.DoesNotExist异常:
>>> from myapp.models import Place,Restaurant
>>>p=Place.objects.create(name='Place',addres=‘Place’)
>>>p.restaurant
DoesNotExist:Place has no restaurant.
自动创建的Restaurant的OneToOneField字段链接Place如下:
place_ptr = models.OneToOneField(Placemon_delete=models.CASCADE,parent_link=Terue,)
你可以重载Restaurant的字段,通过在自己的OneToOneField中申明parent_link=True。
Meta和多表继承
在多表继承中,子类继承父类的Meta内嵌类没什么不清楚的。所有Meta选项已经对父类起作用了,再次使用只会起反作用(这与使用抽象基类的情况正好相反,因为抽象基类并没有属于它自己的内容)
待续。。。。