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

我正在寻找这样做:

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支持吗?如果没有,有没有办法获得类似的结果?

I’m looking to do this:

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)

This is the version I would like to use (although I’m open to any suggestion): http://docs.djangoproject.com/en/dev/topics/db/models/#id7

Is this supported in Django? If not, is there a way to achieve similar results?


回答 0

更新的答案:正如人们在评论中指出的那样,原始答案未能正确回答问题。实际上,只有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)

Updated answer: as people noted in comments, the original answer wasn’t properly answering the question. Indeed, only the LongNamedRestaurant model was created in database, Place was not.

A solution is to create an abstract model representing a “Place”, eg. AbstractPlace, and inherit from it:

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)

Please also read @Mark answer, he gives a great explanation why you can’t change attributes inherited from a non-abstract class.

(Note this is only possible since Django 1.10: before Django 1.10, modifying an attribute inherited from an abstract class wasn’t possible.)

Original answer

Since Django 1.10 it’s possible! You just have to do what you asked for:

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)

回答 1

不,不是

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

在常规的Python类继承中,子类可以覆盖父类的任何属性。在Django中,不允许将这些属性用作Field实例(至少目前是不允许的)。如果基类具有一个名为的字段author,则不能author在从该基类继承的任何类中创建另一个名为的模型字段。

No, it is not:

Field name “hiding” is not permitted

In normal Python class inheritance, it is permissible for a child class to override any attribute from the parent class. In Django, this is not permitted for attributes that are Field instances (at least, not at the moment). If a base class has a field called author, you cannot create another model field called author in any class that inherits from that base class.


回答 2

除非是抽象的,否则这是不可能的,这就是为什么:LongNamedRestaurant这也是a Place,不仅作为类,而且在数据库中。位置表包含每个纯项Place和每个项的条目LongNamedRestaurantLongNamedRestaurant只需使用创建一个额外的表格,food_type并引用该地方表格。

如果这样做Place.objects.all(),您还将获得每个是的地方,LongNamedRestaurant它将是Place(没有food_type)的实例。因此,Place.name并且LongNamedRestaurant.name共享相同的数据库列,因此必须具有相同的类型。

我认为这对于普通模特来说是有道理的:每个餐厅都是一个地方,并且至少应该拥有该地方拥有的所有东西。也许这种一致性也是为什么1.10之前的抽象模型不可能实现的原因,尽管它不会在那里带来数据库问题。如@lampslave所述,它在1.10中成为可能。我个人建议您注意:如果Sub.x覆盖Super.x,请确保Sub.x是Super.x的子类,否则Sub不能代替Super。

解决方法AUTH_USER_MODEL如果您只需要更改电子邮件字段,则可以创建一个涉及大量代码重复的自定义用户模型()。另外,您可以保留电子邮件原样,并确保所有形式的电子邮件都是必需的。如果其他应用程序使用数据库,则不能保证数据库的完整性,也不能保证数据库的完整性(如果不需要使用用户名)。

That is not possible unless abstract, and here is why: LongNamedRestaurant is also a Place, not only as a class but also in the database. The place-table contains an entry for every pure Place and for every LongNamedRestaurant. LongNamedRestaurant just creates an extra table with the food_type and a reference to the place table.

If you do Place.objects.all(), you also get every place that is a LongNamedRestaurant, and it will be an instance of Place (without the food_type). So Place.name and LongNamedRestaurant.name share the same database column, and must therefore be of the same type.

I think this makes sense for normal models: every restaurant is a place, and should have at least everything that place has. Maybe this consistency is also why it was not possible for abstract models before 1.10, although it would not give database problems there. As @lampslave remarks, it was made possible in 1.10. I would personally recommend care: if Sub.x overrides Super.x, make sure Sub.x is a subclass of Super.x, otherwise Sub cannot be used in place of Super.

Workarounds: You can create a custom user model (AUTH_USER_MODEL) which involves quite a bit of code duplication if you only need to change the email field. Alternatively you can leave email as it is and make sure it’s required in all forms. This doesn’t guarantee database integrity if other applications use it, and doesn’t work the other way around (if you want to make username not required).


回答 3

参见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

See 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

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

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

看起来Django不支持该功能。

Pasted your code into a fresh app, added app to INSTALLED_APPS and ran syncdb:

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

Looks like Django does not support that.


回答 5

这段超酷的代码段使您可以“覆盖”抽象父类中的字段。

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

This supercool piece of code allows you to ‘override’ fields in abstract parent classes.

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")

When the fields have been removed from the abstract parent class you are free to redefine them as you need.

This is not my own work. Original code from here: https://gist.github.com/specialunderwear/9d917ddacf3547b646ba


回答 6

也许您可以处理contribute_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正常工作。我没有尝试过这个例子,就我而言,我只是重写了一个约束参数,所以… wait&see!

Maybe you could deal with contribute_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 works fine. I dont tried this example, in my case I just override a constraint parameter so … wait & see !


回答 7

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

我有以下类:

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

I know it’s an old question, but i had a similar problem and found a workaround:

I had the following classes:

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

    class Meta:
        abstract = True

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

But I wanted Year’s inherited image-field to be required while keeping the image field of the superclass nullable. In the end I used ModelForms to enforce the image at the validation stage:

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

It seems this is only applicable for some situations (certainly where you need to enforce stricter rules on the subclass field).

Alternatively you can use the clean_<fieldname>() method instead of clean(), e.g. if a field town would be required to be filled in:

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

您不能覆盖Model字段,但是可以通过覆盖/指定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')})

然后,错误消息将被名称为“ email”的表单字段捕获。

You can not override Model fields, but its easily achieved by overriding/specifying clean() method. I had the issue with email field and wanted to make it unique on Model level and did it like this:

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')})

The error message is then captured by Form field with name “email”


回答 9

我的解决方案与next一样简单monkey patching,请注意如何更改模型中的field max_length属性:nameLongNamedRestaurant

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

My solution is as simple as next monkey patching, notice how I changed max_length attribute fo name field in LongNamedRestaurant model:

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

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。