абстрактні моделі django проти регулярного успадкування


86

Окрім синтаксису, у чому різниця між використанням абстрактної моделі django та використанням простого успадкування Python з моделями django? Плюси і мінуси?

ОНОВЛЕННЯ: Я думаю, моє запитання було неправильно зрозуміле, і я отримав відповіді на різницю між абстрактною моделлю та класом, який успадковується від django.db.models.Model. Я насправді хочу знати різницю між класом моделі, який успадковується від абстрактного класу django (Meta: abstract = True), і простого класу Python, який успадковується від скажімо, 'object' (а не 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(...

1
Це чудовий огляд компромісів між використанням двох підходів до успадкування charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Відповіді:


152

Я насправді хочу знати різницю між класом моделі, який успадковується від абстрактного класу django (Meta: abstract = True), і простого класу Python, який успадковується від скажімо, 'object' (а не 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

4
@rpq Вам доведеться перевірити вихідний код Django, але в IIRC він використовує певний злом метакласу models.Model, тому поля, визначені в суперкласі, не будуть вибиратися (якщо вони також не є підкласами models.Model).
Aya,

@rpq Я знаю, що це було 7 років тому, коли ти запитав, але в дзвінку ModelBase'' __new__він виконує початкову перевірку атрибутів класу, перевіряючи, чи має значення contribute_to_classатрибута атрибут - той, Fieldsякий ти визначаєш у Django, тому вони включені до спеціального словника, contributable_attrsякий зрештою передається під час міграції для створення DDL.
Ю Чень

1
КОЖНИЙ раз Я дивлюсь на DJANGO Я просто хочу плакати, чому ????? навіщо всі дурниці, чому фреймворк повинен поводитися зовсім інакше, ніж мова? А як щодо принципу найменшого здивування? Така поведінка (як майже ВСЕ інше в 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полягає в тому, що перший обробляється як об’єкт бази даних (тому таблиці для нього синхронізуються з базою даних), і він має поведінку a Model. Спадкування від простого Python не objectдає класу (та його підкласам) жодної з цих якостей.


1
Це models.OneToOneField(Vehicle)також еквівалентно успадкуванню класу моделі, правда? І це призведе до двох окремих таблиць, чи не так?
Рафай,

1
@MohammadRafayAleem: Так, але при використанні успадкування Django зробить, наприклад, num_wheelsатрибут на a car, тоді як з a OneToOneFieldвам доведеться зробити це розмежування самостійно.
міпаді

Як я можу змусити за допомогою регулярного успадкування всі поля відтворюватись для 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, приховування імен полів не дозволено успадкуванням моделі.

Наприклад, я експериментував із випадками використання наступним чином:

У мене була модель, яка успадковується від auth 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'

Як бачите, ця помилка справді пояснює, що відбувається.

На мою думку, це була величезна різниця :)


У мене є питання, не пов’язане з основним питанням тут, але як вам вдалося реалізувати це (щоб замінити пов’язане_ім'я поля груп) наприкінці?
кало

@kalo IIRC Я щойно змінив назву на щось інше. Просто немає можливості видалити або замінити успадковані поля.
Адріан,

Так, я був майже готовий відмовитись від цього, коли знайшов спосіб зробити це за допомогою методу contribut_to_class.
kalo

1

Головна відмінність полягає в тому, коли ви успадковуєте клас User. Одна версія поводитиметься як простий клас, а інша - як модель Django.

Якщо ви успадкуєте базову "об'єктну" версію, ваш клас Employee буде просто стандартним класом, а first_name не стане частиною таблиці бази даних. Ви не можете створити форму або використовувати будь-які інші функції Django з нею.

Якщо ви успадковуєте версію models.Model, ваш клас Employee матиме всі методи моделі Django , і він успадкує поле first_name як поле бази даних, яке можна використовувати у формі.

Згідно з документацією, a абстрактна модель "забезпечує спосіб виділення загальної інформації на рівні 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(...)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.