Django三种风格模型继承

Django三种风格的模型继承

  • 只要继承了model.Model, 就会生成一个新的表,但是,如果在Meta方法中添加abstract=True,就不会产生新的表,而是作为一个基类存放多个表共同拥有的方法和字段等

  • 抽象类继承:父类继承自models.Model,但不会在底层数据库中生成相应的数据表,父类的属性列存储在其子类的数据表中。

  • 多表继承:父类和子类都是独立自主、功能完整、可正常使用的模型,都有自己的数据库表,内部隐含了一个一对一的关系。

  • 代理模型继承:父类用于在底层数据库中管理数据表;而子类不定义数据列,只定义查询数据集的排列方式等元数据

  • 应用场景:有这样一个场景,之前已经设计好了用户的信息表,但是再设计另外一个业务表的时候,信息有点重复,可以采用表的继承,让一个表作为基类,业务表就可以继承它

1. 抽象基类:

  • 只需要在模型的Meta类里添加abstract=True元数据项,就可以将一个模型转换为抽象基类。Django不会为这种类创建实际的数据库表,它们也没有管理器,不能被实例化也无法直接保存,它们就是用来被继承的。抽象基类完全就是用来保存子模型们共有的内容部分,达到重用的目的。当它们被继承时,它们的字段会全部复制到子模型中
from django.db import models

# Create your models here.

# 创建基类
class Base(models.Model):
    create_time = models.TimeField(auto_now_add=True)
    update_time = models.TimeField(auto_now=True)

    class Meta:
        abstract = True


# 继承基类 (Base)
class CourseType(Base):
    title = model.CharField()

    class Meta:
        db_table = 'coursetype'

CourseType模型将拥有create_time,update_time两个字段,并且Base模型不能当做一个正常的模型使用

抽象基类的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'

这里有几点要特别说明:

  • 抽象基类中有的元数据,子模型没有的话,直接继承;
  • 抽象基类中有的元数据,子模型也有的话,直接覆盖;
  • 子模型可以额外添加元数据;
  • 抽象基类中的abstract=True这个元数据不会被继承。也就是说如果想让一个抽象基类的子模型,同样成为一个抽象基类,那你必须显式的在该子模型的Meta中同样声明一个abstract = True`;
  • 有一些元数据对抽象基类无效,比如db_table,首先是抽象基类本身不会创建数据表,其次它的所有子类也不会按照这个元数据来设置表名。

2.多表继承

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)
# 如果p也是一个Restaurant对象,那么下面的调用可以获得该Restaurant对象。
>>> p.restaurant
<Restaurant: ...>
但是,如果这个Place是个纯粹的Place对象,并不是一个Restaurant对象,那么上面的调用方式会弹出Restaurant.DoesNotExist异常

2.1 Meta和多表继承

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

    class Meta:
        # 移除父类对子类的排序影响
        ordering = []

2.2 多表继承和反向关联

  • 因为多表继承使用了一个隐含的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 queryname 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参数.

customers = models.ManyToManyField(Place, related_name='provider')。

3.代理模型

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

  • 代理模型就是为此而生的。你可以创建、删除、更新代理模型的实例,并且所有的数据都可以像使用原始模型(非代理类模型)一样被保存。不同之处在于你可以在代理模型中改变默认的排序方式和默认的manager管理器等等,而不会对原始模型产生影响

声明一个代理模型只需要将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:
        # 现在,普通的Person查询是无序的,而OrderedPerson查询会按照`last_name`排序。
        ordering = ["last_name"]
        proxy = True

一些约束:

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

警告

  • 在Python语言层面,子类可以拥有和父类相同的属性名,这样会造成覆盖现象。但是对于Django,如果继承的是一个非抽象基类,那么子类与父类之间不可以有相同的字段名!
class A(models.Model):
    name = models.CharField(max_length=30)

class B(A):
    name = models.CharField(max_length=30)
  • 如果你执行python manage.py makemigrations会弹出下面的错误:
django.core.exceptions.FieldError: Local field 'name' in class 'B' clashes with field of the same name from base class 'A'

但是!如果父类是个抽象基类就没有问题了(1.10版新增特性)

class A(models.Model):
    name = models.CharField(max_length=30)

    class Meta:
        abstract = True

class B(A):
    name = models.CharField(max_length=30

详情请看:https://www.cnblogs.com/navysummer/p/10200167.html

posted @ 2020-10-12 21:57  BeginnerY  阅读(217)  评论(0编辑  收藏  举报