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

Как работает наследование в моделях?

2.2 Middle🔥 161 комментариев
#Django#Базы данных (SQL)

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

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

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

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

Наследование в Django моделях позволяет переиспользовать код и создавать иерархии моделей. Существует три основных типа наследования.

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

Абстрактная модель не создаёт таблицу в БД, это просто контейнер для общих полей.

from django.db import models

class BaseModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_active = models.BooleanField(default=True)
    
    class Meta:
        abstract = True  # Это абстрактная модель

class Article(BaseModel):
    title = models.CharField(max_length=200)
    content = models.TextField()
    
    class Meta:
        db_table = 'articles'

class Comment(BaseModel):
    text = models.TextField()
    article = models.ForeignKey(Article, on_delete=models.CASCADE)

Результат в БД:

  • Таблица articles: id, title, content, created_at, updated_at, is_active
  • Таблица comments: id, text, article_id, created_at, updated_at, is_active

2. Наследование с Multi-table Inheritance

Каждая модель имеет свою таблицу, связанные через OneToOneField.

class Person(models.Model):
    name = models.CharField(max_length=100)
    birth_date = models.DateField()

class Student(Person):
    student_id = models.CharField(max_length=20, unique=True)
    enrollment_date = models.DateField()

class Teacher(Person):
    employee_id = models.CharField(max_length=20, unique=True)
    department = models.CharField(max_length=100)

Результат в БД:

  • Таблица person: id, name, birth_date
  • Таблица student: person_ptr_id (FK), student_id, enrollment_date
  • Таблица teacher: person_ptr_id (FK), employee_id, department

Использование:

# Создание
student = Student.objects.create(
    name="John",
    birth_date="2000-01-01",
    student_id="S12345",
    enrollment_date="2020-09-01"
)

# Доступ к полям
print(student.name)  # John
print(student.student_id)  # S12345

# Запрос
students = Student.objects.all()
teachers = Teacher.objects.all()

# Получение всех Person
persons = Person.objects.all()  # Вернёт и студентов и учителей

# Проверка типа
for person in Person.objects.all():
    if isinstance(person, Student):
        print(f"Student: {person.student_id}")
    elif isinstance(person, Teacher):
        print(f"Teacher: {person.employee_id}")

3. Proxy модели

Proxy модель использует ту же таблицу, что и родитель, но позволяет переопределить поведение.

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    is_published = models.BooleanField(default=False)
    
    class Meta:
        ordering = ['-created_at']

class PublishedArticle(Article):
    """Proxy для опубликованных статей"""
    
    class Meta:
        proxy = True
        ordering = ['-created_at']
    
    def save(self, *args, **kwargs):
        self.is_published = True
        super().save(*args, **kwargs)

class DraftArticle(Article):
    """Proxy для черновиков"""
    
    class Meta:
        proxy = True
    
    objects = models.Manager()
    
    def get_queryset(self):
        return super().get_queryset().filter(is_published=False)

Использование proxy:

# Та же таблица, разные представления
published = PublishedArticle.objects.all()
drafts = DraftArticle.objects.all()

# Создание через proxy
article = PublishedArticle.objects.create(title="My Article")
print(article.is_published)  # True

4. Сравнение типов наследования

ТипТаблицаИспользованиеПлюсыМинусы
AbstractНетОбщие поляПросто, без JOINНельзя запрашивать базовый класс
Multi-tableНесколькоИерархия типовПолиморфизм, наследованиеJOIN операции, медленнее
ProxyОднаАльтернативный интерфейсБыстро, фильтрацияНельзя добавлять поля

5. Практические примеры

Example 1: Content Management System

class Content(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    
    class Meta:
        abstract = True

class Article(Content):
    body = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)

class Video(Content):
    url = models.URLField()
    duration = models.IntegerField()  # в секундах

class Podcast(Content):
    audio_file = models.FileField(upload_to="podcasts/")
    duration = models.IntegerField()

Example 2: User roles

class User(models.Model):
    email = models.EmailField(unique=True)
    is_active = models.BooleanField(default=True)
    
    class Meta:
        abstract = True

class AdminUser(User):
    has_full_access = models.BooleanField(default=False)
    
    def can_delete_users(self):
        return self.has_full_access

class RegularUser(User):
    profile_picture = models.ImageField(upload_to="profiles/")
    bio = models.TextField(blank=True)

6. Best Practices

1. Используйте Abstract для общих полей:

class TimeStampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        abstract = True

2. Избегайте глубокой иерархии: максимум 2-3 уровня

3. Proxy для фильтрации: когда нужен другой queryset

4. Multi-table для истинной иерархии: когда разные типы имеют разные поля

5. Явные related_name: избегайте конфликтов

class StudentPost(models.Model):
    author = models.ForeignKey(
        Student,
        on_delete=models.CASCADE,
        related_name="posts"
    )