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


99

나는 이것을 찾고 있어요 :

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

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


장고 1.10에서 다음과 같은 답변을 받아 주시겠습니까? :)
holms

@holms는 기본 클래스가 추상적 인 경우에만!
Micah Walter

답변:


64

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

8
장소는 추상적이어야합니다.
DylanYoung

4
나는 질문에 게시 된 코드가 이제 Django 1.10 이후로 작동한다고 말하고 있기 때문에 다른 질문에 대답하지 않았다고 생각합니다. 그가 사용하고 싶은 것에 대해 게시 한 링크에 따르면, 그는 Place 클래스를 추상적으로 만드는 것을 잊었습니다.
qmarlats

2
이것이 왜 허용되는 대답인지 확실하지 않습니다 ... OP는 다중 테이블 상속을 사용하고 있습니다. 이 답변은 추상 기본 클래스에만 유효합니다.
MrName

1
추상 클래스는 Django 1.10 이전에 사용 가능했습니다.
rbennell

1
@NoamG 내 원래 대답에서는 Place추상적이어서 데이터베이스에서 생성 되지 않았습니다 . 그러나 영업 이익은 원 모두 PlaceLongNamedRestaurant데이터베이스에 생성 할 수 있습니다. 따라서 나는 AbstractPlace"기본"(즉, 추상) 모델 Place이며 LongNamedRestaurant상속받는 모델 을 추가하기 위해 내 대답을 업데이트 했습니다. 이제 OP가 요청한대로 Place및 둘 다 LongNamedRestaurant데이터베이스에 생성됩니다.
qmarlats

61

아니요, 아닙니다 .

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

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


11
왜 불가능한 지에 대한 내 대답을 참조하십시오. 사람들은 이것이 말이되기 때문에 이것을 좋아합니다. 그것은 바로 명백하지 않습니다.
Mark

4
@ leo-the-manic 나는 User._meta.get_field('email').required = True작동 할 수 있다고 생각 하지만 확실하지 않습니다.
Jens Timmerman

@ leo-the-manic, @JensTimmerman, @utapyngo 클래스의 속성 값을 설정해도 상속 된 필드에는 영향을주지 않습니다. _meta예를 들어 MyParentClass._meta.get_field('email').blank = False( email관리자에서 상속 된 필드를 필수 로 만들기 위해) 부모 클래스의에서 행동해야합니다.
Peterino

1
죄송합니다. 위의 @utapyngo 코드는 정확하지만 나중에 클래스 본문 외부 에 배치해야합니다 ! 내가 제안한대로 부모 클래스 필드를 설정하면 원치 않는 부작용이 발생할 수 있습니다.
Peterino 2014 년

모든 하위 클래스에 특정 이름의 필드가 있음을 보장하기 위해 각 하위 클래스의 필드가 추상 부모 클래스의 동일한 이름을 가진 필드와 다른 유형이되기를 원합니다. utapyngo의 코드는 이러한 요구를 충족하지 않습니다.
Daniel

28

그것은 추상이 아니면 불가능 LongNamedRestaurant하며 Place, 그 이유는 다음과 같습니다 . 클래스로서뿐만 아니라 데이터베이스에서도. place-table에는 모든 pure Place및 모든 LongNamedRestaurant. 장소 테이블에 대한 및 참조를 LongNamedRestaurant사용하여 추가 테이블을 만듭니다 food_type.

당신이한다면 Place.objects.all(), 당신은 또한 인 모든 장소를 얻습니다 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이메일 필드 만 변경해야하는 경우 상당한 코드 복제를 포함 하는 사용자 지정 사용자 모델 ( )을 만들 수 있습니다 . 또는 이메일을 그대로두고 모든 양식에 필요한지 확인할 수 있습니다. 다른 응용 프로그램에서 사용하는 경우 데이터베이스 무결성을 보장하지 않으며 다른 방식으로 작동하지 않습니다 (사용자 이름이 필요하지 않게하려면).


1.10의 변경 사항 때문인 것 같습니다. "추상 기본 클래스에서 상속 된 모델 필드 재정의 허용" docs.djangoproject.com/en/2.0/releases/1.10/#models
lampslave

그 당시에는 아직 나오지 않았기 때문에 의심 스럽지만 덧붙여서 좋은 것입니다. 감사합니다!
Mark

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

2
AttributeError : ca n't set attribute ((((but I 'm trying set choices
Alexey a

이것은 Django 1.11에서 작동하지 않습니다 (이전 버전에서 작동했습니다). 승인 된 응답이 작동합니다
acaruci

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가 지원하지 않는 것 같습니다.


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


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가 잘 작동합니다. 이 예제를 시도하지 않았습니다. 제 경우에는 제약 조건 매개 변수를 재정의하므로 ... 기다립니다!


1
또한 Contribute_to_class에 대한 인수가 이상해 보입니다 (또한 잘못된 방법입니까?) 메모리에서 이것을 입력 한 것 같습니다. 테스트 한 실제 코드를 제공해 주시겠습니까? 이 일을했다면 어떻게했는지 정확히 알고 싶습니다.
Michael Bylstra 2013 년

이것은 나를 위해 작동하지 않습니다. 작업 예제에도 관심이 있습니다.
garromark 2013

참조하시기 바랍니다 blog.jupo.org/2011/11/10/django-model-field-injection을 가 contribute_to_class해야한다 (<ModelClass>, <fieldToReplace>)
고 씨

3
Place._meta.get_field('name').max_length = 255클래스 본문에서을 재정의하지 않고 트릭을 수행해야합니다 __init__(). 또한 더 간결 할 것입니다.
Peterino 2014 년

4

나는 그것이 오래된 질문이라는 것을 알고 있지만 비슷한 문제가 있었고 해결 방법을 찾았습니다.

나는 다음과 같은 수업을 받았다.

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

    class Meta:
        abstract = True

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

그러나 수퍼 클래스의 이미지 필드를 nullable로 유지하면서 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

1

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"이라는 이름의 양식 필드에 오류 메시지가 캡처됩니다.


문제는 char 필드의 max_length 확장에 관한 것입니다. 이것이 데이터베이스에 의해 시행된다면이 "솔루션"은 도움이되지 않습니다. 해결 방법은 기본 모델에서 더 긴 max_length를 지정하고 clean () 메서드를 사용하여 더 짧은 길이를 적용하는 것입니다.
DylanYoung

0

내 솔루션은 다음과 같이 간단 합니다. 모델의 필드에 대한 속성을 monkey patching어떻게 변경했는지 확인하십시오 .max_lengthnameLongNamedRestaurant

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.