django抽象モデルと通常の継承


87

構文に加えて、django抽象モデルを使用することとdjangoモデルでプレーンなPython継承を使用することの違いは何ですか?長所と短所?

更新:私の質問は誤解されていたと思います。抽象モデルとdjango.db.models.Modelから継承したクラスの違いについての回答を受け取りました。 私は実際に、django抽象クラス(Meta:abstract = True)から継承するモデルクラスと、たとえば「object」(models.Modelではなく)から継承するプレーンPythonクラスの違いを知りたいと思っています。

次に例を示します。

class User(object):
   first_name = models.CharField(..

   def get_username(self):
       return self.username

class User(models.Model):
   first_name = models.CharField(...

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(...

1
これは、2つの継承アプローチを使用することの間のトレードオフの概要です。charlesleifer.com
blog /

回答:


155

私は実際に、django抽象クラス(Meta:abstract = True)から継承するモデルクラスと、たとえば「object」(models.Modelではなく)から継承するプレーンPythonクラスの違いを知りたいと思っています。

Djangoはのサブクラスのテーブルのみを生成するmodels.Modelため、前者は...

class User(models.Model):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(max_length=255)

...次の行に沿って単一のテーブルが生成されます...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(255) NOT NULL,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

...後者は...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User):
   title = models.CharField(max_length=255)

...テーブルは生成されません。

多重継承を使用して、このようなことを行うことができます...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User, models.Model):
   title = models.CharField(max_length=255)

...これはテーブルを作成しますが、Userクラスで定義されたフィールドを無視するため、次のようなテーブルになります...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

3
おかげで、これは私の質問に答えます。first_nameがEmployee用に作成されない理由はまだ不明です。
rpq 2013年

4
@rpq Djangoのソースコードを確認する必要がありますが、IIRCではメタクラスハッカーが使用models.Modelされているため、スーパークラスで定義されたフィールドは取得されません(サブクラスでもない場合models.Model)。

1
@rpqこれは7年前のことですが、ModelBase__new__呼び出しでは、クラスの属性の初期チェックを実行し、属性値に属性があるかどうかをチェックします。これは、Djangoで定義したすべてのcontribute_to_class属性Fieldsです。と呼ばれる特別な辞書に含まれています。この辞書はcontributable_attrs、移行中に最終的に渡されてDDLを生成します。
ゆうチェン

2
DJANGOを見るたびに泣きたいだけなのになぜ????? なぜすべてのナンセンスなのか、なぜフレームワークは言語とはまったく異なる方法で動作する必要があるのですか?驚き最小の原則はどうですか?この動作(djangoの他のほとんどすべてのように)は、Pythonを理解している人が期待するものではありません。
E.Serra

36

抽象モデルは、各サブチャイルドの列のセット全体を含むテーブルを作成しますが、「プレーン」なPython継承を使用すると、リンクされたテーブルのセット(別名「マルチテーブル継承」)が作成されます。2つのモデルがある場合を考えてみましょう。

class Vehicle(models.Model):
  num_wheels = models.PositiveIntegerField()


class Car(Vehicle):
  make = models.CharField(…)
  year = models.PositiveIntegerField()

Vehicleが抽象モデルの場合、単一のテーブルがあります。

app_car:
| id | num_wheels | make | year

ただし、プレーンなPython継承を使用する場合は、次の2つのテーブルがあります。

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

車のホイールの数も含まvehicle_idれる行へのリンクはどこにありapp_vehicleますか。

これで、Djangoはこれをオブジェクト形式でうまくまとめnum_wheelsて、の属性としてアクセスできるようにしますがCar、データベースの基になる表現は異なります。


更新

更新された質問に対処するために、Django抽象クラスからの継承とPythonからの継承の違いobjectは、前者がデータベースオブジェクトとして扱われ(したがって、そのテーブルがデータベースに同期される)、の動作をすることModelです。プレーンなPythonから継承するobjectと、クラス(およびそのサブクラス)にこれらの品質は与えられません。


1
やってmodels.OneToOneField(Vehicle)も、右、モデルクラスを継承と同等のでしょうか?そして、それは2つの別々のテーブルになりますね。
ラファイ2014

1
@MohammadRafayAleem:はい。ただし、継承を使用する場合、Djangoは、たとえば、num_wheelsの属性を作成しますがcar、を使用するOneToOneField場合は、自分自身を間接参照する必要があります。
mipadi 2014

通常の継承で、app_carテーブルに対してすべてのフィールドが再生成されnum_wheelsvehicle_idポインタではなくフィールドも含まれるようにするにはどうすればよいですか?
ドングレム2015

@DorinGrecu:基本クラスを抽象モデルにして、そこから継承します。
mipadi 2015

@mipadi問題は、基本クラスを抽象化できないことです。これはプロジェクト全体で使用されています。
ドングレム2015

13

主な違いは、モデルのデータベーステーブルの作成方法です。なしで継承を使用する場合abstract = TrueDjangoと、親モデルと子モデルの両方に個別のテーブルが作成され、各モデルで定義されたフィールドが保持されます。

使用する場合 abstract = True基本クラスに場合、Djangoは、フィールドが基本クラスで定義されているか継承クラスで定義されているかに関係なく、基本クラスから継承するクラスのテーブルのみを作成します。

長所と短所は、アプリケーションのアーキテクチャによって異なります。次のモデル例を考えます。

class Publishable(models.Model):
    title = models.CharField(...)
    date = models.DateField(....)

    class Meta:
        # abstract = True

class BlogEntry(Publishable):
    text = models.TextField()


class Image(Publishable):
    image = models.ImageField(...)

場合はPublishable、クラスが抽象的ではありませんDjangoは列を持つpublishables用のテーブルを作成するtitledate、別のテーブルのためにBlogEntryImage。このソリューションの利点は、ブログエントリであるか画像であるかに関係なく、基本モデルで定義されたフィールドについてすべての公開可能ファイルでクエリを実行できることです。ただし、たとえば画像のクエリを実行する場合、Publishable abstract = TrueDjangoは結合を実行する必要があります... Djangoを作成すると、のテーブルは作成されませんPublishableが、すべてのフィールド(継承されたものも含む)を含むブログエントリと画像のみが作成されます。getなどの操作に結合が必要ないため、これは便利です。

モデルの継承に関するDjangoのドキュメントも参照してください。


9

他の回答では見たことのないものを追加したかっただけです。

Pythonクラスとは異なり、フィールド名の非表示は許可されていませんモデルの継承でて。

たとえば、次のようなユースケースで問題を実験しました。

私は、Djangoの認証を継承するモデルだったPermissionMixinを

class PermissionsMixin(models.Model):
    """
    A mixin class that adds the fields and methods necessary to support
    Django's Group and Permission model using the ModelBackend.
    """
    is_superuser = models.BooleanField(_('superuser status'), default=False,
        help_text=_('Designates that this user has all permissions without '
                    'explicitly assigning them.'))
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        blank=True, help_text=_('The groups this user belongs to. A user will '
                                'get all permissions granted to each of '
                                'his/her group.'))
    user_permissions = models.ManyToManyField(Permission,
        verbose_name=_('user permissions'), blank=True,
        help_text='Specific permissions for this user.')

    class Meta:
        abstract = True

    # ...

それから私は他のものの間、私はそれを上書きしたかった私のミックスインだったrelated_namegroupsフィールドを。だからそれは多かれ少なかれこのようでした:

class WithManagedGroupMixin(object):
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        related_name="%(app_label)s_%(class)s",
        blank=True, help_text=_('The groups this user belongs to. A user will '
                            'get all permissions granted to each of '
                            'his/her group.'))

私はこの2つのミックスインを次のように使用していました:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

そうそう、私はこれがうまくいくと思っていましたが、うまくいきませんでした。しかし、私が得たエラーはモデルをまったく指していなかったため、問題はより深刻でした。何が問題になっているのかわかりませんでした。

これを解決しようとしているときに、ミックスインをランダムに変更して、抽象モデルのミックスインに変換することにしました。エラーは次のように変更されました。

django.core.exceptions.FieldError: Local field 'groups' in class 'Member' clashes with field of similar name from base class 'PermissionMixin'

ご覧のとおり、このエラーは何が起こっているのかを説明しています。

私の意見では、これは大きな違いでした:)


ここでの主な質問とは関係のない質問がありますが、最後にこれを(グループフィールドのrelated_nameをオーバーライドするために)どのように実装しましたか?
kalo 2015年

@kaloIIRC名前をまったく別のものに変更しました。継承されたフィールドを削除または置換する方法はありません。
アドリアン2015年

はい、contribut_to_classメソッドを使用してそれを行う方法を見つけたとき、私はこれをあきらめる準備がほぼできていました。
kalo 2015年

1

主な違いは、Userクラスを継承する場合です。1つのバージョンは単純なクラスのように動作し、もう1つのバージョンはDjangoモデルのように動作します。

基本の「オブジェクト」バージョンを継承する場合、Employeeクラスは単なる標準クラスになり、first_nameはデータベーステーブルの一部にはなりません。フォームを作成したり、他のDjango機能を使用したりすることはできません。

models.Modelバージョンを継承する場合、EmployeeクラスにはDjangoモデルのすべてのメソッドが含まれますれ、フォームで使用できるデータベースフィールドとしてfirst_nameフィールドが継承されます。

ドキュメントによると、抽象モデルは「Pythonレベルで一般的な情報を除外する方法を提供しますが、データベースレベルでは子モデルごとに1つのデータベーステーブルしか作成しません」。


0

個別のテーブルを作成せず、ORMがデータベースに結合を作成する必要がないため、ほとんどの場合、抽象クラスを優先します。そして、抽象クラスの使用はDjangoでは非常に簡単です

class Vehicle(models.Model):
    title = models.CharField(...)
    Name = models.CharField(....)

    class Meta:
         abstract = True

class Car(Vehicle):
    color = models.CharField()

class Bike(Vehicle):
    feul_average = models.IntegerField(...)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.