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

时间:2010-02-26 20:50:54

标签: python django django-inheritance

我希望这样做:

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 个答案:

答案 0 :(得分:55)

不,it is not

  

不允许使用字段名称“隐藏”

     

在普通的Python类继承中,允许孩子使用   用于覆盖父类的任何属性的类。在Django中,这个   不允许属于Field个实例的属性(至少,   现在不行)。如果基类有一个名为author的字段,那么你   无法在任何类中创建另一个名为author的模型字段   继承自该基类。

答案 1 :(得分:49)

更新的答案:正如人们在评论中指出的那样,原来的答案没有正确回答这个问题。实际上,只有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 answer,他给出了一个很好的解释,为什么你不能改变从非抽象类继承的属性。

(注意这是唯一可能的,因为Django 1.10:在Django 1.10之前,修改从抽象类继承的属性是不可能的。)

  

原始答案

     

自Django 1.10 it's possible以来!   你只需做你要求的事情:

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)

答案 2 :(得分:22)

除非抽象,否则这是不可能的,这就是原因:LongNamedRestaurant也是Place,不仅是一个类,而且还在数据库中。地方表包含每个纯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),如果您只需要更改电子邮件字段,则会涉及相当多的代码重复。或者,您可以保留电子邮件,并确保所有表格都需要。如果其他应用程序使用它,则不保证数据库完整性,并且不能以相反的方式工作(如果您不想要用户名)。

答案 3 :(得分:19)

请参阅https://stackoverflow.com/a/6379556/15690

class BaseMessage(models.Model):
    is_public = models.BooleanField(default=False)
    # some more fields...

    class Meta:
        abstract = True

class Message(BaseMessage):
    # some fields...
Message._meta.get_field('is_public').default = True

答案 4 :(得分:9)

将您的代码粘贴到一个全新的应用程序中,将应用程序添加到INSTALLED_APPS并运行syncdb:

django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'

看起来Django不支持这一点。

答案 5 :(得分:7)

也许你可以处理contrib_to_class:

class LongNamedRestaurant(Place):

    food_type = models.CharField(max_length=25)

    def __init__(self, *args, **kwargs):
        super(LongNamedRestaurant, self).__init__(*args, **kwargs)
        name = models.CharField(max_length=255)
        name.contribute_to_class(self, 'name')

Syncdb工作正常。我没试过这个例子,在我的情况下,我只是覆盖一个约束参数,所以...等待&看!

答案 6 :(得分:7)

此超级冷却代码允许您覆盖'抽象父类中的字段。

def AbstractClassWithoutFieldsNamed(cls, *excl):
    """
    Removes unwanted fields from abstract base classes.

    Usage::
    >>> from oscar.apps.address.abstract_models import AbstractBillingAddress

    >>> from koe.meta import AbstractClassWithoutFieldsNamed as without
    >>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
    ...     pass
    """
    if cls._meta.abstract:
        remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
        for f in remove_fields:
            cls._meta.local_fields.remove(f)
        return cls
    else:
        raise Exception("Not an abstract model")

当字段从抽象父类中删除后,您可以根据需要自由重新定义它们。

这不是我自己的工作。来自此处的原始代码:https://gist.github.com/specialunderwear/9d917ddacf3547b646ba

答案 7 :(得分:4)

我知道这是一个老问题,但我遇到了类似的问题并找到了解决方法:

我有以下课程:

class CommonInfo(models.Model):
    image = models.ImageField(blank=True, null=True, default="")

    class Meta:
        abstract = True

class Year(CommonInfo):
    year = models.IntegerField() 

但我希望在保持超类的图像字段可空的同时使用Year的继承图像字段。最后,我使用ModelForms在验证阶段强制执行图像:

class YearForm(ModelForm):
    class Meta:
        model = Year

    def clean(self):
        if not self.cleaned_data['image'] or len(self.cleaned_data['image'])==0:
            raise ValidationError("Please provide an image.")

        return self.cleaned_data

admin.py:

class YearAdmin(admin.ModelAdmin):
    form = YearForm

这似乎只适用于某些情况(当然需要在子类字段上强制执行更严格的规则)。

或者,您可以使用clean_<fieldname>()方法代替clean(),例如如果需要填写字段town

def clean_town(self):
    town = self.cleaned_data["town"]
    if not town or len(town) == 0:
        raise forms.ValidationError("Please enter a town")
    return town

答案 8 :(得分:1)

您不能覆盖模型字段,但可以通过覆盖/指定clean()方法轻松实现。我遇到了电子邮件领域的问题,并希望在模型级别上使其独一无二,并且这样做:

def clean(self):
    """
    Make sure that email field is unique
    """
    if MyUser.objects.filter(email=self.email):
        raise ValidationError({'email': _('This email is already in use')})

然后,表单字段使用名称&#34; email&#34;

捕获错误消息

答案 9 :(得分:0)

我的解决方案与下一个monkey patching一样简单,请注意如何更改max_length模型中name字段的LongNamedRestaurant属性:

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

class LongNamedRestaurant(Place):
    food_type = models.CharField(max_length=25)
    Place._meta.get_field('name').max_length = 255