在 Django-Model 继承中——它允许你覆盖父模型的属性吗?

我想这么做:

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 支持这个吗? 如果不支持,是否有办法实现类似的结果?

65774 次浏览

将代码粘贴到一个新的应用程序中,将应用程序添加到 INSTALLED _ APPS 并运行 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.

不,it is not:

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

在正常的 Python 类继承中,允许子继承 类重写来自父类的任何属性 is not permitted for attributes that are Field instances (at least, 如果基类有一个名为 author的字段,则 不能在任何类中创建另一个名为 author的模型字段 从该基类继承。

也许你可以处理一下对课堂的贡献:

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 工作正常。我没有尝试这个例子,在我的情况下,我只是覆盖了一个约束参数,所以... 等等!

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

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

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

这是不可能的,除非是抽象的,这里是原因: LongNamedRestaurant也是一个 Place,不仅作为一个类,而且在数据库中。Place-table 包含每个纯 Place和每个 LongNamedRestaurant的条目。LongNamedRestaurant只是创建了一个额外的带有 food_type的表和一个对餐位表的引用。

如果你做 Place.objects.all(),你也得到每个地方是一个 LongNamedRestaurant,它将是一个 Place的实例(没有 food_type)。因此,Place.nameLongNamedRestaurant.name共享相同的数据库列,因此必须属于相同的类型。

我认为这对于普通模式来说是有意义的: 每家餐厅都是一个地方,至少应该拥有那个地方拥有的一切。也许这种一致性也是为什么在1.10之前抽象模型不可能存在的原因,尽管在1.10之前抽象模型不会给数据库带来问题。正如@lampSlave 所言,这是在1.10中实现的。我个人建议注意: 如果 Sub.x 覆盖 Super.x,请确保 Sub.x 是 Super.x 的子类,否则 Sub 不能用于替代 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).

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

不能重写 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”的 Form 字段捕获

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

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

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

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

原始答案

由于 Django 1.10 < a href = “ https://docs.djangoproject.com/en/dev/questions/db/model/# field-name-hide-is-not- rel = “ noReferrer”> ,因此它是 可能! 你只需要做你要求的事:

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)

我的解决方案和下一个 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