Djangoの場合-モデルの継承-親モデルの属性をオーバーライドできますか?


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

これはDjangoでサポートされていますか?そうでない場合、同様の結果を達成する方法はありますか?


django 1.10から可能です:)
ホルムス

基本クラスが抽象の場合のみ@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 answerも読んでください。彼は、非抽象クラスから継承された属性を変更できない理由を説明しています。

(これはDjango 1.10以降でのみ可能です。Django1.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 2017年

2
なぜこれが受け入れられる答えなのかわかりません... OPはマルチテーブル継承を使用しています。この回答は、抽象基本クラスに対してのみ有効です。
MrName

1
Django 1.10よりずっと前に抽象クラスが利用可能でした
rbennell

1
@NoamG私の最初の答えでPlaceは、抽象的だったので、データベースで作成されませんでした。しかし、OPは両方PlaceLongNamedRestaurantデータベースで作成されることを望んでいました。したがって、私はAbstractPlaceモデルを追加するように回答を更新しました。これは、「ベース」(つまり、抽象)モデルでありPlaceLongNamedRestaurant継承元のモデルです。今両方PlaceLongNamedRestaurantOPはを求めたとして、データベースに作成されます。
qmarlats

61

いいえ、そうではありませ

フィールド名「非表示」は許可されていません

通常のPythonクラス継承では、子クラスが親クラスの属性をオーバーライドすることが許可されています。Djangoでは、これはFieldインスタンスである属性には許可されていません(少なくとも現時点では)。基本クラスにというフィールドがある場合、その基本クラスから継承するクラスでauthor呼び出される別のモデルフィールドを作成することはできませんauthor


11
それが不可能な理由については私の答えを参照してください。それが理にかなっているので、人々はこれが好きです、それはすぐに明白ではありません。
マーク

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

抽象的でない限りそれは不可能であり、ここに理由があります:LongNamedRestaurantPlace、クラスとしてだけでなく、データベース内でもです。place-tableには、すべてのpure Placeとeveryのエントリが含まれてい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

まだ出ていなかったので疑わしいですが、それは付け加えるのにいいものです、ありがとう!
マーク

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:属性を設定できません(((((私は選択肢を設定しようとしています
Alexey

これは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
また、contribut_to_classへの引数が奇妙に見えます(これも間違った方法ですか?)メモリから入力したようです。テストした実際のコードを提供してもらえますか?これを機能させることができたなら、私はあなたがそれをどのようにしたかを正確に知りたいと思います。
Michael Bylstra、2013年

これではうまくいきません。実際の例にも興味があります。
garromark 2013年

blog.jupo.org/2011/11/10/django-model-field-injectionを参照してください。contribute_to_class(<ModelClass>、<fieldToReplace>)
goh

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

しかし、私はスーパークラスの画像フィールドをヌル可能に保ちながら、Year'sの継承された画像フィールドが必要であることを望みました。最後に、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

私の解決策は次のように単純です、モデルの属性fo フィールドを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.