Programing

장고에서-모델 상속-부모 모델의 속성을 재정의 할 수 있습니까?

lottogame 2020. 9. 11. 19:23
반응형

장고에서-모델 상속-부모 모델의 속성을 재정의 할 수 있습니까?


나는 이것을 찾고 있어요 :

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

장고에서 지원 되나요? 그렇지 않은 경우 유사한 결과를 얻을 수있는 방법이 있습니까?


업데이트 된 답변 : 사람들이 댓글에서 언급했듯이 원래 답변은 질문에 제대로 답변하지 않았습니다. 실제로 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)

아니요, 아닙니다 .

필드 이름 "숨기기"는 허용되지 않습니다.

일반적인 Python 클래스 상속에서는 자식 클래스가 부모 클래스의 모든 속성을 재정의하는 것이 허용됩니다. Django에서는 Field인스턴스 인 속성에 대해 허용되지 않습니다 (적어도 현재는 아님). 기본 클래스에라는 필드가있는 경우 해당 기본 클래스 에서 상속되는 클래스에서 author호출 author되는 다른 모델 필드를 만들 수 없습니다 .


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


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

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.


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 !


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


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

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"


My solution is as simple as next monkey patching, notice how I changed max_length attribute fo 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

참고URL : https://stackoverflow.com/questions/2344751/in-django-model-inheritance-does-it-allow-you-to-override-a-parent-models-a

반응형