В 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? Если нет, есть ли способ достичь подобных результатов?
Ответы
Ответ 1
Обновленный ответ: как отмечают люди в комментариях, первоначальный ответ не отвечал правильно на вопрос. Действительно, в базе данных была создана только модель 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)
Ответ 2
Нет, это не:
Имя поля "hiding" не разрешено
В обычном наследовании класса Python допустимо для дочернего класс для переопределения любого атрибута из родительского класса. В Django это не допускается для атрибутов Field
экземпляров (по крайней мере, не в данный момент). Если базовый класс имеет поле, называемое author
, вы не может создать другое модельное поле под названием author
в любом классе, которое наследует от этого базового класса.
Ответ 3
Это невозможно без абстракции, и вот почему: 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
), которая включает в себя немало дублирования кода, если вам нужно только изменить поле электронной почты. В качестве альтернативы вы можете оставить письмо как есть и убедиться, что оно требуется во всех формах. Это не гарантирует целостность базы данных, если ее используют другие приложения, и не работает наоборот (если вы хотите, чтобы имя пользователя не требовалось).
Ответ 4
См. fooobar.com/questions/90933/...:
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
Ответ 5
Вставьте код в новое приложение, добавьте приложение в INSTALLED_APPS и запустите syncdb:
django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'
Похоже, что Django не поддерживает это.
Ответ 6
Возможно, вы можете иметь дело с 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 отлично работает. Я не пробовал этот пример, в моем случае я просто переопределяю параметр ограничения, поэтому... подождите и посмотрите!
Ответ 7
Этот фрагмент кода supercool позволяет вам "переопределять" поля в абстрактных родительских классах.
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
Ответ 8
Я знаю, что это старый вопрос, но у меня была аналогичная проблема и нашел обходное решение:
У меня были следующие классы:
class CommonInfo(models.Model):
image = models.ImageField(blank=True, null=True, default="")
class Meta:
abstract = True
class Year(CommonInfo):
year = models.IntegerField()
Но я хотел, чтобы Год, унаследованный образ-поле, требовался, сохраняя поле изображения суперкласса с нулевым значением. В конце я использовал 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
Ответ 9
Вы не можете переопределить поля Модели, но это легко достигается путем переопределения/определения метода 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')})
Затем сообщение об ошибке будет записано в поле Form с именем "email"
Ответ 10
Мое решение так же просто, как следующее 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