在Django中,模型继承是否允许覆盖父模型的属性?

122 投票
10 回答
70642 浏览
提问于 2025-04-15 19:47

我想要做的是这个:

class Place(models.Model):
   name = models.CharField(max_length=20)
   rating = models.DecimalField()

class LongNamedRestaurant(Place):  # Subclassing `Place`.
   name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
   food_type = models.CharField(max_length=25)

这是我想用的版本(不过我也愿意听听其他建议):http://docs.djangoproject.com/en/dev/topics/db/models/#id7

这个在Django中支持吗?如果不支持,有没有其他方法可以达到类似的效果?

10 个回答

31

这件事是不可能的,除非是抽象的,原因如下:LongNamedRestaurant 不仅是一个类,它在数据库中也是一个 Place。地方表(place-table)里每个纯粹的 Place 和每个 LongNamedRestaurant 都有一条记录。LongNamedRestaurant 只是多创建了一个表,里面有 food_type 和对地方表的引用。

如果你执行 Place.objects.all(),你也会得到每个作为 LongNamedRestaurant 的地方,而它会是一个 Place 的实例(没有 food_type)。所以 Place.nameLongNamedRestaurant.name 共享同一个数据库列,因此它们必须是同一种类型。

我觉得这对于普通模型来说是有道理的:每个餐馆都是一个地方,应该至少拥有地方所具备的所有属性。也许这种一致性就是为什么在1.10之前抽象模型无法实现,尽管那样不会导致数据库问题。正如 @lampslave 所说,在1.10中实现了这一点。我个人建议要小心:如果 Sub.x 重写了 Super.x,确保 Sub.x 是 Super.x 的子类,否则 Sub 就不能替代 Super。

解决方法:你可以创建一个自定义用户模型(AUTH_USER_MODEL),但如果你只是想改邮箱字段,这样会涉及到很多代码重复。或者你可以保持邮箱字段不变,并确保在所有表单中都是必填的。这并不能保证数据库的完整性,如果其他应用使用它的话,而且如果你想让用户名不是必填的,这种方法也不适用。

64

不可以,这是不允许的

字段名称“隐藏”是不允许的

在普通的Python类继承中,子类可以覆盖父类的任何属性。但在Django中,对于那些是Field实例的属性,这是不允许的(至少目前是这样)。如果一个基类有一个叫author的字段,那么在任何继承自这个基类的类中,你都不能再创建一个叫author的模型字段。

83

更新的回答:正如评论中提到的,原来的回答并没有正确回答问题。实际上,数据库中只创建了 LongNamedRestaurant 模型,而 Place 并没有被创建。

一个解决方案是创建一个抽象模型来表示“地点”,比如 AbstractPlace,然后从这个模型继承:

class AbstractPlace(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class Place(AbstractPlace):
    pass

class LongNamedRestaurant(AbstractPlace):
    name = models.CharField(max_length=255)
    food_type = models.CharField(max_length=25)

请还阅读 @Mark 的 回答,他很好地解释了为什么你不能修改从非抽象类继承的属性。

(注意,这种情况自 Django 1.10 开始才可以:在 Django 1.10 之前,修改从抽象类继承的属性是不可能的。)

原始回答

自 Django 1.10 起,这是可能的!你只需要做你想做的事情:

class Place(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class LongNamedRestaurant(Place):  # Subclassing `Place`.
    name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
    food_type = models.CharField(max_length=25)

撰写回答