Комментарии (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 по умолчанию пока не понадобится другое