django抽象模型与常规继承


86

除了语法之外,使用django抽象模型和对django模型使用纯Python继承之间有什么区别?利弊?

更新:我认为我的问题被误解了,并且我收到了关于抽象模型与从django.db.models.Model继承的类之间的区别的答复。 我实际上想知道从django抽象类(Meta:abstract = True)继承的模型类和从“ object”(而不是models.Model)继承的普通Python类之间的区别。

这是一个例子:

class User(object):
   first_name = models.CharField(..

   def get_username(self):
       return self.username

class User(models.Model):
   first_name = models.CharField(...

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(...

1
这是对使用两种继承方法之间的取舍的一个很好的概述charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Answers:


152

我实际上想知道从django抽象类(Meta:abstract = True)继承的模型类和从“ object”(而不是models.Model)继承的普通Python类之间的区别。

Django只会为的子类生成表models.Model,因此前者...

class User(models.Model):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(max_length=255)

...将导致按照...的方式生成一个表

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(255) NOT NULL,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

后者

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User):
   title = models.CharField(max_length=255)

...不会导致生成任何表格。

您可以使用多重继承执行类似的操作...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User, models.Model):
   title = models.CharField(max_length=255)

...这将创建一个表,但将忽略User该类中定义的字段,因此最终将得到一个这样的表...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

3
谢谢,这回答了我的问题。至于为什么没有为Employee创建first_name仍不清楚。
rpq

4
@rpq您必须检查Django的源代码,但是IIRC在中使用了一些元类黑客技术models.Model,因此不会选择超类中定义的字段(除非它们也是的子类models.Model)。
2013年

@rpq我知道这是7年前问到的,但是在ModelBase__new__调用中,它将对类的属性进行初始检查,检查属性值是否具有contribute_to_class属性-Fields您在Django中定义的属性都具有,因此包含在称为的特殊词典中,该词典contributable_attrs最终会在迁移过程中传递以生成DDL。
于晨

1
每当我看着DJANGO时,我只想哭,为什么?为什么所有这些废话,为什么框架必须以与语言完全不同的方式运行?最少惊讶的原则呢?这种行为(就像ALMOST django中的其他所有内容)并不是任何了解python的人所期望的。
E.Serra

36

抽象模型会为每个子子级创建一个具有整个列集的表,而使用“普通” Python继承会创建一组链接表(又称为“多表继承”)。考虑一下您有两个模型的情况:

class Vehicle(models.Model):
  num_wheels = models.PositiveIntegerField()


class Car(Vehicle):
  make = models.CharField(…)
  year = models.PositiveIntegerField()

如果Vehicle是抽象模型,则将只有一个表:

app_car:
| id | num_wheels | make | year

但是,如果您使用普通的Python继承,则将有两个表:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

vehicle_id行中的链接在哪里也app_vehicle将具有汽车的车轮数量。

现在,Django将以对象形式很好地将它们组合在一起,以便您可以num_wheels作为上的属性进行访问Car,但是数据库中的基础表示形式将有所不同。


更新资料

为了解决您的更新问题,从Django抽象类继承和从Python继承之间的区别在于object,前者被视为数据库对象(因此,该表的对象已同步到数据库),并且行为为Model。从普通的Python继承不会object给类(及其子类)带来这些品质。


1
这样做models.OneToOneField(Vehicle)也等同于继承模型类,对吗?这将导致两个单独的表,不是吗?
拉斐2014年

1
@MohammadRafayAleem:是的,但是当使用继承时,Django将在上创建例如num_wheels属性car,而使用aOneToOneField则必须取消引用自己。
mipadi 2014年

如何通过常规继承强制为app_car表重新生成所有字段,以便它也具有该num_wheels字段,而不是具有vehicle_id指针?
唐·格雷姆

@DorinGrecu:使基类成为抽象模型,并从其继承。
mipadi

@mipadi的问题是我无法使基类抽象化,它在整个项目中都在使用。
唐·格雷姆

13

主要区别在于如何创建模型的数据库表。如果您不使用继承而使用abstract = TrueDjango,则会为父模型和子模型创建一个单独的表,其中包含每个模型中定义的字段。

如果您abstract = True将基类用于Django,那么Django只会为从基类继承的类创建一个表-无论字段是在基类还是继承类中定义的。

优缺点取决于应用程序的体系结构。给定以下示例模型:

class Publishable(models.Model):
    title = models.CharField(...)
    date = models.DateField(....)

    class Meta:
        # abstract = True

class BlogEntry(Publishable):
    text = models.TextField()


class Image(Publishable):
    image = models.ImageField(...)

如果Publishable该类不是抽象的Django会创建一个表与列publishablestitledate和单独的表BlogEntryImage。该解决方案的优势在于您可以查询所有可发布对象基本模型中定义的字段,无论它们是博客条目还是图像。但是,因此,如果您要查询图像,例如Django,则必须进行联接...如果使Publishable abstract = TrueDjango不会为创建表Publishable,而是仅为博客条目和图像创建表,其中包含所有字段(也包括继承的字段)。这将很方便,因为不需要诸如get之类的操作的联接。

另请参阅Django有关模型继承的文档


9

只是想添加一些我在其他答案中没有看到的东西。

与python类不同,不允许隐藏字段名称模型继承。

例如,我对用例进行了如下实验:

我有一个从Django的auth PermissionMixin继承的模型:

class PermissionsMixin(models.Model):
    """
    A mixin class that adds the fields and methods necessary to support
    Django's Group and Permission model using the ModelBackend.
    """
    is_superuser = models.BooleanField(_('superuser status'), default=False,
        help_text=_('Designates that this user has all permissions without '
                    'explicitly assigning them.'))
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        blank=True, help_text=_('The groups this user belongs to. A user will '
                                'get all permissions granted to each of '
                                'his/her group.'))
    user_permissions = models.ManyToManyField(Permission,
        verbose_name=_('user permissions'), blank=True,
        help_text='Specific permissions for this user.')

    class Meta:
        abstract = True

    # ...

然后,我有了我的mixin,除其他外,我还希望它覆盖related_namegroups字段。所以或多或少是这样的:

class WithManagedGroupMixin(object):
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        related_name="%(app_label)s_%(class)s",
        blank=True, help_text=_('The groups this user belongs to. A user will '
                            'get all permissions granted to each of '
                            'his/her group.'))

我使用这2个mixins如下:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

是的,我希望这能奏效,但没有成功。但是问题更加严重,因为我得到的错误根本没有指向模型,我根本不知道出了什么问题。

在尝试解决此问题时,我随机决定更改我的mixin并将其转换为抽象模型mixin。错误更改为:

django.core.exceptions.FieldError: Local field 'groups' in class 'Member' clashes with field of similar name from base class 'PermissionMixin'

如您所见,此错误确实说明了发生了什么。

在我看来,这是一个巨大的差异:)


我有一个与这里的主要问题无关的问题,但是最后您是如何实现这一目标的(以覆盖groups字段的related_name)?
卡洛(Kalo),2015年

@kalo IIRC我只是将名称完全更改为其他名称。只是没有办法删除或替换继承的字段。
阿德里安

是的,当我找到一种使用contribution_to_class方法来做到这一点时,我几乎准备放弃它。
卡洛2015年

1

主要区别在于何时继承User类。一个版本的行为类似于简单的类,而另一个版本的行为类似于Django modeel。

如果继承基本的“对象”版本,则Employee类将只是一个标准类,并且first_name不会成为数据库表的一部分。您不能创建表单,也不能使用其他任何Django功能。

如果您继承了models.Model版本,则Employee类将具有Django Model的所有方法并将继承first_name字段作为可在表单中使用的数据库字段。

根据文档,抽象模型“提供了一种在Python级别上排除常见信息的方法,而在数据库级别,每个子模型仍然只创建一个数据库表。”


0

在大多数情况下,我将首选抽象类,因为它不会创建单独的表,并且ORM无需在数据库中创建联接。在Django中使用抽象类非常简单

class Vehicle(models.Model):
    title = models.CharField(...)
    Name = models.CharField(....)

    class Meta:
         abstract = True

class Car(Vehicle):
    color = models.CharField()

class Bike(Vehicle):
    feul_average = models.IntegerField(...)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.