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

Какие знаешь стратегии расширения модели в Django?

3.0 Senior🔥 241 комментариев
#Django

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

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

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

Стратегии расширения моделей в Django

Расширение моделей — это естественный процесс эволюции приложения. Django предлагает несколько подходов для расширения функциональности существующих моделей без нарушения SOLID принципов.

1. Наследование моделей (Model Inheritance)

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

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

Абстрактная модель не создаёт таблицу в БД, служит только как шаблон:

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 Post(TimestampedModel):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey('User', on_delete=models.CASCADE)

class Comment(TimestampedModel):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    author = models.ForeignKey('User', on_delete=models.CASCADE)
    text = models.TextField()

Таблица TimestampedModel НЕ создаётся. Post и Comment наследуют поля.

Плюсы: повторное использование кода (DRY), нет производительности (no JOIN).

Минусы: не можем запросить все TimestampedModel вместе.

B. Multi-table наследование

Каждый класс создаёт свою таблицу и связана через OneToOneField:

class Person(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()

class Employee(Person):
    employee_id = models.CharField(max_length=50)
    department = models.CharField(max_length=100)
    salary = models.DecimalField(max_digits=10, decimal_places=2)

class Contractor(Person):
    company = models.CharField(max_length=200)
    hourly_rate = models.DecimalField(max_digits=8, decimal_places=2)

В БД: person, employee (с person_ptr_id), contractor (с person_ptr_id).

Плюсы: полиморфизм, можно запросить родителей независимо.

Минусы: дополнительные JOIN (медленнее), сложность в миграциях.

C. Proxy модели

Прокси модель использует ту же таблицу, но добавляет методы:

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    status = models.CharField(
        max_length=20,
        choices=[('draft', 'Draft'), ('published', 'Published')],
        default='draft'
    )
    created_at = models.DateTimeField(auto_now_add=True)

class PublishedPost(Post):
    class Meta:
        proxy = True

Прокси модель — это alias для той же таблицы. Нет дополнительной таблицы.

Плюсы: нет дополнительных таблиц, быстро.

Минусы: нельзя добавлять новые поля.

2. Foreign Keys и Relations

Добавлять связанные данные через relations:

class UserProfile(models.Model):
    user = models.OneToOneField(
        'auth.User',
        on_delete=models.CASCADE,
        related_name='profile'
    )
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', null=True)
    birth_date = models.DateField(null=True)
    phone = models.CharField(max_length=20, blank=True)

Отделяем профильные данные от встроенного User.

Плюсы: модели остаются простыми, легко управлять правами доступа.

Минусы: дополнительные запросы (требуют select_related).

3. Mixins и Composition

Использовать миксины для добавления функциональности:

from django.db import models

class TimestampMixin:
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    def is_recent(self):
        from datetime import timedelta
        from django.utils import timezone
        return self.created_at > timezone.now() - timedelta(days=7)

class SlugMixin:
    slug = models.SlugField(unique=True)
    
    def get_absolute_url(self):
        return f'/{self.slug}/'

class Article(TimestampMixin, SlugMixin, models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)

Плюсы: переиспользование кода, модели остаются простыми.

4. Django Signals

Выполнить код при определённых событиях модели:

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender='auth.User')
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

@receiver(post_save, sender='auth.User')
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

Плюсы: слабая связь между моделями, легко добавлять функциональность.

Минусы: сложно отлаживать, неявная логика.

5. Managers и QuerySets

Добавлять кастомную логику запросов:

class PublishedQuerySet(models.QuerySet):
    def published(self):
        return self.filter(status='published')
    
    def recent(self, days=7):
        from datetime import timedelta
        from django.utils import timezone
        date = timezone.now() - timedelta(days=days)
        return self.filter(created_at__gte=date)

class PublishedManager(models.Manager):
    def get_queryset(self):
        return PublishedQuerySet(self.model, using=self._db)
    
    def published(self):
        return self.get_queryset().published()

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    status = models.CharField(
        max_length=20,
        choices=[('draft', 'Draft'), ('published', 'Published')],
        default='draft'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    
    objects = PublishedManager()

Использование: Post.objects.published(), Post.objects.recent(days=30).

6. JSONField для гибких данных

Добавлять гибкие данные без миграций:

class Product(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    attributes = models.JSONField(
        default=dict,
        blank=True,
        help_text='Additional product attributes'
    )

Применение: Product.objects.filter(attributes__color='silver').

7. Database migrations для расширения

Когда расширяем модель, создаём миграции:

python manage.py makemigrations
python manage.py migrate
python manage.py migrate --list

Какую стратегию выбрать?

  • Abstract Base Class — повторяемое поведение (timestamps)
  • Multi-table inheritance — полиморфные типы (Person/Employee)
  • Proxy Model — переопределение querysets
  • OneToOne Profile — дополнительные данные для User
  • JSONField — гибкие атрибуты
  • Managers/QuerySets — кастомные запросы
  • Signals — побочные эффекты при создании/обновлении

Ключ к успешному расширению модели в Django — понимать компромисс между сложностью и производительностью.