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

发布于 2024-08-23 07:03:41 字数 580 浏览 8 评论 0原文

我希望这样做:

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

亚希 2024-08-30 07:03:41

更新的答案:正如人们在评论中指出的那样,原始答案没有正确回答问题。事实上,数据库中只创建了 LongNamedRestaurant 模型,而没有创建 Place

解决方案是创建一个代表“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 起它是
可能的!
你只需要做你要求的事情:

类 Place(models.Model):
    名称 = models.CharField(max_length=20)
    评级 = models.DecimalField()

    类元:
        抽象=真实

class LongNamedRestaurant(Place): # 子类化`Place`。
    name = models.CharField(max_length=255) # 注意,我重写 `Place.name` 以赋予它更长的长度。
    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)
锦上情书 2024-08-30 07:03:41

不,不是< /a>:

不允许字段名称“隐藏”

在普通的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.

酒废 2024-08-30 07:03:41

除非是抽象的,否则这是不可能的,原因如下:LongNamedRestaurant 也是一个 Place,不仅作为一个类,而且在数据库中。地点表包含每个纯 Place 和每个 LongNamedRestaurant 的条目。 LongNamedRestaurant 只是创建一个带有 food_type 的额外表和对 place 表的引用。

如果您执行 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),如果您只需要更改电子邮件字段,该模型会涉及大量代码重复。或者,您可以保留电子邮件原样,并确保所有表格都需要它。如果其他应用程序使用它,这并不能保证数据库的完整性,并且反之亦然(如果您想让用户名不需要)。

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

北城半夏 2024-08-30 07:03:41

请参阅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
你没皮卡萌 2024-08-30 07:03:41

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

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 of 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
留蓝 2024-08-30 07:03:41

将您的代码粘贴到一个新的应用程序中,将应用程序添加到 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.

挽清梦 2024-08-30 07:03:41

这段超酷的代码允许您“覆盖”抽象父类中的字段。

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

人间☆小暴躁 2024-08-30 07:03:41

也许你可以处理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工作正常。我没有尝试这个例子,在我的例子中,我只是覆盖一个约束参数,所以......等待&看 !

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 !

影子的影子 2024-08-30 07:03:41

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

我有以下类:

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_() 方法代替 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
凉城凉梦凉人心 2024-08-30 07:03:41

您不能覆盖模型字段,但可以通过覆盖/指定 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"

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文