我希望这样做:
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是否支持此功能?如果没有,有没有办法达到类似的结果?
答案 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.name
和LongNamedRestaurant.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