← Назад к вопросам

Что такое наследование моделей в Django?

2.7 Senior🔥 171 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Наследование моделей в Django

Наследование моделей — это механизм Django ORM, который позволяет создавать иерархию моделей с общими полями и методами. Django поддерживает три типа наследования, каждый с разными стратегиями хранения данных в БД.

Три типа наследования в Django

1. Abstract Base Classes (Абстрактные базовые классы)

from django.db import models

class TimeStampedModel(models.Model):
    """Абстрактная база с временными метками."""
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        abstract = True  # ← ВАЖНО! Это просто шаблон, таблица не создаётся

class User(TimeStampedModel):
    """Конкретная модель, наследуется от абстрактной базы."""
    name = models.CharField(max_length=100)
    email = models.EmailField()

class Post(TimeStampedModel):
    """Другая модель, также наследуется от абстрактной базы."""
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)

# В БД будут созданы две таблицы:
# 1. users (id, name, email, created_at, updated_at)
# 2. posts (id, title, content, author_id, created_at, updated_at)

Характеристики:

  • Таблица не создаётся для абстрактного класса
  • Поля копируются в каждую дочернюю таблицу
  • Простое и быстрое решение для переиспользования полей
  • Можно запрашивать только дочерние модели, не базу

2. Multi-table Inheritance (Наследование с несколькими таблицами)

from django.db import models

class Vehicle(models.Model):
    """Базовая модель, НЕ абстрактная."""
    brand = models.CharField(max_length=100)
    year = models.IntegerField()
    
    # Для автоматического OneToOneField в дочерней модели

class Car(Vehicle):
    """Наследуется от Vehicle, создаёт свою таблицу."""
    num_doors = models.IntegerField()
    
    # В БД автоматически создаётся OneToOneField
    # vehicle_ptr = models.OneToOneField(Vehicle, ...)

class Motorcycle(Vehicle):
    """Другая подмодель."""
    has_sidecar = models.BooleanField()

# В БД создаются три таблицы:
# 1. vehicles (id, brand, year)
# 2. cars (vehicle_ptr_id → vehicles.id, num_doors)
# 3. motorcycles (vehicle_ptr_id → vehicles.id, has_sidecar)

# Использование:
car = Car.objects.create(brand="Toyota", year=2023, num_doors=4)
print(car.brand)  # "Toyota" (из Vehicle)
print(car.num_doors)  # 4 (из Car)

# Полиморфизм:
for vehicle in Vehicle.objects.all():
    if hasattr(vehicle, 'car'):
        print(f"Car: {vehicle.brand}")
    elif hasattr(vehicle, 'motorcycle'):
        print(f"Motorcycle: {vehicle.brand}")

Характеристики:

  • Несколько таблиц (по одной для базы и каждого наследника)
  • JOIN запросы нужны для получения полных данных (медленнее)
  • Сохраняет отношения между таблицами через OneToOne
  • Полиморфизм через наследование

3. Proxy Models (Прокси модели)

from django.db import models
from django.db.models import Q

class Person(models.Model):
    """Основная модель."""
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    is_active = models.BooleanField(default=True)
    
    def get_full_name(self):
        return f"{self.first_name} {self.last_name}"

class ActivePerson(Person):
    """Прокси для активных людей."""
    
    class Meta:
        proxy = True  # ← Таблица не создаётся, используется Person
    
    def get_queryset(self):
        return super().get_queryset().filter(is_active=True)

class InactivePerson(Person):
    """Прокси для неактивных людей."""
    
    class Meta:
        proxy = True
    
    objects = models.Manager()  # Переопределяем менеджер
    
    def get_queryset(self):
        return super().get_queryset().filter(is_active=False)

# Использование:
active = ActivePerson.objects.all()  # Только активные
inactive = InactivePerson.objects.all()  # Только неактивные

# Оба используют одну таблицу 'person'
# Но с разными фильтрами по умолчанию

Характеристики:

  • Одна таблица для всех моделей
  • Быстрые запросы без JOIN
  • Переопределение методов и менеджеров
  • Фильтры по умолчанию для разных представлений

Сравнение всех трёх подходов

# ABSTRACT BASE CLASS
# Используй когда: нужны общие поля и методы
class BaseModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        abstract = True

# Плюсы:
# ✓ Просто (копирует поля)
# ✓ Быстрые запросы (одна таблица на модель)
# ✓ Понятно какие поля у каждой модели

# Минусы:
# ✗ Нельзя запрашивать базу напрямую
# ✗ Нет отношений между таблицами


# MULTI-TABLE INHERITANCE
# Используй когда: нужен полиморфизм и отношения
class Animal(models.Model):
    name = models.CharField(max_length=100)

class Dog(Animal):
    breed = models.CharField(max_length=100)

# Плюсы:
# ✓ Настоящий ООП полиморфизм
# ✓ Можно запрашивать базовый класс
# ✓ Отношения сохраняются

# Минусы:
# ✗ Медленные запросы (JOIN всегда)
# ✗ Сложнее в управлении
# ✗ Много таблиц


# PROXY MODEL
# Используй когда: разные представления одних данных
class User(models.Model):
    name = models.CharField(max_length=100)
    is_staff = models.BooleanField()

class AdminUser(User):
    class Meta:
        proxy = True

# Плюсы:
# ✓ Быстрые запросы (одна таблица)
# ✓ Разные менеджеры и методы
# ✓ Понятное разделение ответственности

# Минусы:
# ✗ Не может добавлять новые поля
# ✗ Не для полиморфизма

Практический пример: интернет-магазин

from django.db import models
from django.utils import timezone

# Абстрактная база для временных меток
class TimestampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        abstract = True

# Абстрактная база для товаров
class Product(TimestampedModel):
    """Базовый класс для товаров."""
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.IntegerField(default=0)
    
    class Meta:
        abstract = True

# Конкретные товары
class PhysicalProduct(Product):
    """Физический товар."""
    weight = models.FloatField(help_text="в килограммах")
    dimensions = models.CharField(max_length=100)
    shipping_cost = models.DecimalField(max_digits=10, decimal_places=2)

class DigitalProduct(Product):
    """Цифровой товар."""
    file_url = models.URLField()
    file_size = models.BigIntegerField(help_text="в байтах")
    license = models.CharField(max_length=100)

class Subscription(Product):
    """Подписка."""
    billing_cycle = models.CharField(
        max_length=10,
        choices=[
            ('monthly', 'Ежемесячно'),
            ('yearly', 'Ежегодно'),
        ]
    )
    auto_renewal = models.BooleanField(default=True)

# В БД будут таблицы:
# physical_products (id, name, price, stock, weight, dimensions, shipping_cost, created_at, updated_at)
# digital_products (id, name, price, stock, file_url, file_size, license, created_at, updated_at)
# subscriptions (id, name, price, stock, billing_cycle, auto_renewal, created_at, updated_at)

Пример с multi-table inheritance

from django.db import models

# Базовая публикация
class Publication(models.Model):
    title = models.CharField(max_length=200)
    pub_date = models.DateField()
    author = models.ForeignKey('Author', on_delete=models.CASCADE)

class Article(Publication):
    """Статья наследуется от Publication."""
    content = models.TextField()
    category = models.CharField(max_length=100)

class Book(Publication):
    """Книга наследуется от Publication."""
    isbn = models.CharField(max_length=13)
    pages = models.IntegerField()
    publisher = models.CharField(max_length=100)

# В БД:
# publications (id, title, pub_date, author_id)
# articles (publication_ptr_id → publications.id, content, category)
# books (publication_ptr_id → publications.id, isbn, pages, publisher)

# Запросы:
article = Article.objects.create(
    title="Python Tips",
    pub_date=timezone.now(),
    author=author_obj,
    content="...",
    category="Programming"
)

# Получить как статью
print(article.title)      # "Python Tips"
print(article.content)    # "..."

# Получить всё
for pub in Publication.objects.all():  # Работает!
    if isinstance(pub, Article):
        print(f"Article: {pub.article.title}")
    elif isinstance(pub, Book):
        print(f"Book: {pub.book.title}")

Пример с proxy model

from django.db import models
from django.db.models import Q

class Order(models.Model):
    """Основная модель заказов."""
    customer = models.ForeignKey('Customer', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    total = models.DecimalField(max_digits=10, decimal_places=2)
    status = models.CharField(
        max_length=20,
        choices=[
            ('pending', 'В ожидании'),
            ('confirmed', 'Подтверждён'),
            ('shipped', 'Отправлен'),
            ('delivered', 'Доставлен'),
        ]
    )

class PendingOrder(Order):
    """Только ожидающие заказы."""
    class Meta:
        proxy = True
    
    objects = models.Manager()
    
    def get_queryset(self):
        return super().get_queryset().filter(status='pending')

class ShippedOrder(Order):
    """Только отправленные заказы."""
    class Meta:
        proxy = True
        ordering = ['-created_at']
    
    def mark_delivered(self):
        self.status = 'delivered'
        self.save()

# Использование:
pending = PendingOrder.objects.count()  # Только ожидающие
shipped = ShippedOrder.objects.all()    # Только отправленные

# Одна таблица 'orders', но разные фильтры

Миграции и наследование

# Abstract Base Classes
# Создаёт таблицы только для конкретных моделей
python manage.py makemigrations
# → Создаст миграции для Product, Article, Book
# → НЕ создаст для TimestampedModel (abstract=True)

# Multi-table Inheritance
# Создаёт таблицу базовой модели + таблицы для наследников
python manage.py makemigrations
# → Создаст publications, articles, books
# → Добавит OneToOneField в articles и books

# Proxy Model
# Не создаёт новых таблиц
python manage.py makemigrations
# → Не создаст новые таблицы
# → Может только изменять менеджер и методы

Частые ошибки

# ❌ Ошибка 1: забыли abstract = True
class BaseModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    # Забыли: class Meta: abstract = True

# Результат: Django создаст таблицу BaseModel
# Все наследники добавят OneToOneField → много лишних таблиц

# ✓ Правильно:
class BaseModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    class Meta:
        abstract = True

# ❌ Ошибка 2: proxy model с новыми полями
class VIPUser(User):
    vip_level = models.IntegerField()  # ❌ Ошибка!
    
    class Meta:
        proxy = True

# Proxy не может добавлять новые поля
# Используй multi-table inheritance вместо

# ❌ Ошибка 3: запрос на абстрактную базу
class TimeModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    class Meta:
        abstract = True

TimeModel.objects.all()  # ❌ AttributeError!
# Абстрактная модель не создаёт таблицу

Рекомендации

# ИСПОЛЬЗУЙ Abstract Base Classes когда:
# - Нужны общие поля (timestamps, user, status)
# - Не нужны отношения между моделями
# - Каждая модель независима

# ИСПОЛЬЗУЙ Multi-table Inheritance когда:
# - Нужны настоящие иерархия и полиморфизм
# - Нужны запросы к базовому типу (Vehicle.objects.all())
# - Готов к overhead от JOIN запросов

# ИСПОЛЬЗУЙ Proxy Model когда:
# - Нужны разные менеджеры для одних данных
# - Разные представления (ActiveUser, StaffUser)
# - Не добавляешь новые поля
# - Хочешь быстроты

Ключевые моменты

  • Abstract Base = копирование полей (лучший выбор обычно)
  • Multi-table = несколько таблиц с JOIN (полиморфизм)
  • Proxy = одна таблица, разные менеджеры (фильтры)
  • abstract=True означает что таблица не создаётся
  • proxy=True значит что новая таблица не создаётся
  • Multi-table наследование медленнее из-за JOIN
  • Выбирай Abstract Base по умолчанию пока не понадобится другое