--- status: processing type: concept tags: [django, backend, orm, database, migrations] created: 2026-05-30 updated: 2026-05-31 title: Django Модели и ORM source: ["[[lec1-django_start_models-1.pdf]]", "[[lec4-django_lec_queries.pdf]]", "[[p1-django-1.pdf]]"] --- # Django Модели и ORM **Django ORM (Object-Relational Mapping)** — технология связи объектно-ориентированного кода и реляционной БД. Реализует паттерн Active Record. ## Структура проекта - `manage.py`: Утилита командной строки. - `settings.py`: Конфигурация проекта (БД, `INSTALLED_APPS`). - `urls.py`: Описание маршрутов. - `models.py`: Определение таблиц БД. ```text project/ ├── manage.py ├── project/ │ ├── settings.py │ └── urls.py └── app/ └── models.py ``` ## Определение моделей ```python from django.db import models class Person(models.Model): first_name = models.CharField("Имя", max_length=30) age = models.IntegerField(default=18) bio = models.TextField(blank=True) class Meta: verbose_name = "Человек" ordering = ["-first_name"] ``` ### Типы полей - `AutoField` — автоинкрементный числовой первичный ключ. - `CharField(max_length=N)` — короткая строка фиксированной максимальной длины. - `TextField` — длинный текст без обязательного ограничения длины. - `IntegerField` — целое число. - `DecimalField(max_digits, decimal_places)` — точное десятичное число, удобно для денег и измерений. - `BooleanField` — логическое значение `True`/`False`. - `DateField` — дата без времени. - `DateTimeField` — дата и время. - `EmailField` — строка с валидацией email-адреса. - `FileField` — путь к загруженному файлу. - `ImageField` — файл изображения с дополнительной проверкой формата. ```python class Article(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=200) body = models.TextField() views = models.IntegerField(default=0) price = models.DecimalField(max_digits=10, decimal_places=2) is_published = models.BooleanField(default=False) published_at = models.DateTimeField(null=True, blank=True) author_email = models.EmailField() attachment = models.FileField(upload_to="files/", blank=True) cover = models.ImageField(upload_to="covers/", blank=True) ``` ### Отношения - `ForeignKey(Target, on_delete=...)` — связь "один ко многим": у одной записи `Target` может быть много связанных записей текущей модели. - `OneToOneField(Target, on_delete=...)` — связь "один к одному": каждой записи соответствует максимум одна связанная запись. - `ManyToManyField(Target)` — связь "многие ко многим": записи обеих моделей могут ссылаться на несколько записей другой модели. `on_delete` задаёт, что делать с текущей записью, если удаляется связанная запись `Target`: - `CASCADE` — удалить текущую запись вместе со связанной. - `PROTECT` — запретить удаление связанной записи, пока на неё есть ссылки. - `SET_NULL` — записать `NULL` в поле связи; требует `null=True`. - `SET_DEFAULT` — записать значение по умолчанию; требует `default`. - `DO_NOTHING` — ничего не делать на уровне Django, ответственность остаётся на БД и разработчике. ```python class Author(models.Model): name = models.CharField(max_length=100) class Profile(models.Model): author = models.OneToOneField(Author, on_delete=models.CASCADE) bio = models.TextField() class Book(models.Model): title = models.CharField(max_length=200) author = models.ForeignKey(Author, on_delete=models.PROTECT) co_authors = models.ManyToManyField(Author, related_name="coauthored_books", blank=True) ``` --- ## Миграции Инструмент для синхронизации моделей с БД. - `python manage.py makemigrations` — создать файлы миграций. - `python manage.py migrate` — применить изменения в БД. - `python manage.py showmigrations` — список статусов миграций. --- ## Работа с QuerySet (Запросы) Django QuerySet ленивы (вычисляются только при обращении) и поддерживают цепочки методов. ### Базовые CRUD операции - **Create:** `obj = Person.objects.create(name="Ivan")` или `obj.save()`. - **Read:** - `Person.objects.all()` (все), - `Person.objects.get(id=1)` (один), - `Person.objects.filter(age__gt=20)` (фильтрация). - **Update:** `Person.objects.filter(id=1).update(age=25)`. - **Delete:** `obj.delete()` или `QuerySet.delete()`. ```python # Create person = Person.objects.create(first_name="Ivan", age=25) # Read people = Person.objects.all() person = Person.objects.get(id=1) adults = Person.objects.filter(age__gte=18) # Update Person.objects.filter(id=1).update(age=26) # Delete person.delete() ``` ### Поиск по полям (Field Lookups) - `exact`, `iexact` (регистронезависимо). - `gt` (>), `gte` (>=), `lt` (<), `lte` (<=). - `contains`, `icontains`. - `in` (список), `range` (диапазон). - `isnull=True`. ```python Person.objects.filter(first_name__exact="Ivan") Person.objects.filter(first_name__iexact="ivan") Person.objects.filter(age__gt=18) Person.objects.filter(age__range=(18, 30)) Person.objects.filter(first_name__icontains="iv") Person.objects.filter(id__in=[1, 2, 3]) Person.objects.filter(bio__isnull=True) ``` ### Оптимизация запросов - `select_related('field')` — заранее подтягивает связанную запись через `JOIN`. Подходит для `ForeignKey` и `OneToOneField`, когда у объекта есть одна связанная запись. - `prefetch_related('field')` — делает отдельный запрос за связанными объектами и собирает их в Python. Подходит для `ManyToManyField` и обратных связей, где связанных записей может быть много. - **Проблема N+1** — ситуация, когда Django делает один запрос за списком объектов, а потом ещё по одному запросу на каждый объект в цикле. ```python # select_related: книга + её автор загружаются одним SQL-запросом books = Book.objects.select_related("author") for book in books: print(book.title, book.author.name) ``` ```python # prefetch_related: авторы загружаются отдельно, затем Django связывает их с книгами books = Book.objects.prefetch_related("authors") for book in books: print(book.title, [author.name for author in book.authors.all()]) ``` ### Агрегация и аннотация - `aggregate(Avg('rating'))` — считает одно итоговое значение по всей выборке: среднее, сумму, минимум, максимум, количество. Возвращает словарь, а не QuerySet. - `annotate(total=Count('entries'))` — добавляет вычисляемое поле к каждому объекту в выборке. ```python from django.db.models import Avg, Count # aggregate: одно итоговое значение по всей таблице книг result = Book.objects.aggregate(avg_rating=Avg("rating")) print(result["avg_rating"]) # annotate: вычисляемое поле для каждого автора authors = Author.objects.annotate(total_books=Count("books")) for author in authors: print(author.name, author.total_books) ``` ### Логические условия - `Q-объекты` нужны для сложных условий в `filter()`: `OR` (`|`), `AND` (`&`), `NOT` (`~`). - `F-выражения` позволяют сравнивать и изменять поля прямо на стороне БД, не загружая значение в Python. ```python from django.db.models import F, Q # Q: рейтинг выше 4 или название содержит Django books = Book.objects.filter( Q(rating__gt=4) | Q(title__icontains="Django") ) # F: увеличить счётчик просмотров прямо в БД Book.objects.filter(id=1).update(views=F("views") + 1) ```