Абстрактные модели django против обычного наследования

86

Помимо синтаксиса, в чем разница между использованием абстрактной модели django и использованием простого наследования Python с моделями django? За и против?

ОБНОВЛЕНИЕ: я думаю, что мой вопрос был неправильно понят, и я получил ответы о разнице между абстрактной моделью и классом, наследуемым от django.db.models.Model. На самом деле я хочу знать разницу между классом модели, который наследуется от абстрактного класса django (Meta: abstract = True), и простым классом Python, который наследуется, скажем, от «объекта» (а не от models.Model).

Вот пример:

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(...
rpq
источник
1
Это отличный обзор компромиссов между использованием двух подходов к наследованию charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Ответы:

152

На самом деле я хочу знать разницу между классом модели, который наследуется от абстрактного класса django (Meta: abstract = True), и простым классом Python, который наследуется, скажем, от «объекта» (а не от models.Model).

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 02
4
@rpq Вам нужно будет проверить исходный код Django, но IIRC использует некоторые хакерские методы метакласса models.Model, поэтому поля, определенные в суперклассе, не будут взяты (если они не являются также подклассами models.Model).
Aya
@rpq Я знаю , что это было 7 лет назад , когда вы спросили, но в ModelBase«S __new__вызова, он выполняет начальную проверку класса» атрибутов, проверка , если значение атрибута имеет contribute_to_classпризнак - Fieldsчто вы определяете в Django все сделать, чтобы они включены в специальный словарь, contributable_attrsкоторый в конечном итоге передается во время миграции для генерации DDL.
Ю Чен
1
КАЖДЫЙ РАЗ Смотрю на ДЖАНГО Я просто хочу плакать, почему ????? к чему вся эта чепуха, почему фреймворк должен вести себя совершенно иначе, чем язык? А как насчет принципа наименьшего удивления? Такое поведение (как и ПОЧТИ все остальное в django) не соответствует ожиданиям любого, кто разбирается в Python.
Э. Серра
36

Абстрактная модель создает таблицу со всем набором столбцов для каждого дочернего ребенка, тогда как использование «простого» наследования Python создает набор связанных таблиц (также известный как «многотабличное наследование»). Рассмотрим случай, когда у вас есть две модели:

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, у вас будет две таблицы:

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)также было бы равносильно наследованию класса модели, верно? И это привело бы к созданию двух отдельных таблиц, не так ли?
Rafay
1
@MohammadRafayAleem: Да, но при использовании наследования Django создаст, например, num_wheelsатрибут в a car, тогда как с a OneToOneFieldвам придется делать это, разыменовывая себя.
mipadi
Как я могу заставить с регулярным наследованием, чтобы все поля были повторно сгенерированы для app_carтаблицы, чтобы она также имела num_wheelsполе вместо vehicle_idуказателя?
Дон Грем
@DorinGrecu: сделайте базовый класс абстрактной моделью и унаследуйте от нее.
mipadi
@mipadi проблема в том, что я не могу сделать базовый класс абстрактным, он используется во всем проекте.
Дон Грем
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 создаст таблицу для публикуемых материалов со столбцами titleи, dateа также отдельные таблицы для BlogEntryи Image. Преимущество этого решения состоит в том, что вы можете запрашивать поля, определенные в базовой модели, среди всех публикуемых объектов , независимо от того, являются ли они записями в блогах или изображениями. Но поэтому Django должен будет выполнять соединения, если вы, например, выполняете запросы для изображений ... Если создание Publishable abstract = TrueDjango не будет создавать таблицу для Publishableзаписей блога и изображений, а только для записей в блогах и изображений, содержащих все поля (также унаследованные). Это было бы удобно, потому что для такой операции, как get, не требуется никаких соединений.

Также см . Документацию Django о наследовании моделей .

Бернхард Валлант
источник
9

Просто хотел добавить то, чего не видел в других ответах.

В отличие от классов python, скрытие имени поля не разрешено при наследовании модели.

Например, я экспериментировал со следующим вариантом использования:

У меня была модель, унаследованная от PermissionMixin django :

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_nameв groupsполе. Так было примерно так:

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
@kalo IIRC Я просто поменял название на другое. Просто нет возможности удалить или заменить унаследованные поля.
Адриан
Да, я был почти готов отказаться от этого, когда нашел способ сделать это с помощью метода deposit_to_class.
kalo
1

Основное отличие заключается в том, когда вы наследуете класс User. Одна версия будет вести себя как простой класс, а другая - как модель Django.

Если вы унаследуете базовую «объектную» версию, ваш класс Employee будет просто стандартным классом, а first_name не станет частью таблицы базы данных. Вы не можете создать форму или использовать с ней какие-либо другие функции Django.

Если вы наследуете версию models.Model, ваш класс Employee будет иметь все методы модели Django и унаследует поле first_name как поле базы данных, которое можно использовать в форме.

Согласно документации, абстрактная модель «предоставляет способ вычленить общую информацию на уровне Python, в то же время создавая только одну таблицу базы данных для каждой дочерней модели на уровне базы данных».

Брент Уошберн
источник
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(...)
Камран Хасан
источник