115

我正在寻找这样做:

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

4

10 回答 10

76

更新的答案:正如人们在评论中指出的那样,原始答案没有正确回答问题。实际上,只有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开始就有可能!你只需要按照你的要求去做:

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)
于 2016-09-01T18:26:56.980 回答
62

不,它不是

不允许字段名称“隐藏”

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

于 2010-03-01T18:08:20.913 回答
30

除非是抽象的,否则这是不可能的,这就是为什么:LongNamedRestaurant也是 a Place,不仅作为一个类,而且在数据库中。place-table 包含每个 purePlace和 every的条目LongNamedRestaurantLongNamedRestaurant只需创建一个额外的表,其中food_type包含对 place 表的引用。

如果你这样做Place.objects.all(),你也会得到每个地方是 a 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涉及大量代码重复的自定义用户模型 ( )。或者,您可以保留电子邮件原样,并确保它在所有表格中都是必需的。如果其他应用程序使用它,这并不能保证数据库的完整性,并且不能反过来(如果你想让用户名不需要)。

于 2013-08-10T07:13:11.633 回答
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
于 2014-07-04T23:04:06.700 回答
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 不支持。

于 2010-02-26T21:43:43.310 回答
8

我的解决方案很简单monkey patching,请注意我如何更改模型中字段的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
于 2019-07-17T07:10:21.990 回答
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

于 2015-09-22T15:11:47.807 回答
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 工作正常。我没有尝试这个例子,就我而言,我只是覆盖了一个约束参数,所以......等等看!

于 2010-06-23T05:18:36.177 回答
4

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

我有以下课程:

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

    class Meta:
        abstract = True

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

但我希望 Year 继承的 image-field 是必需的,同时保持超类的 image 字段可以为空。最后,我使用 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

管理员.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
于 2011-12-15T16:07:21.790 回答
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')})

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

于 2015-06-23T22:13:29.597 回答