Files
SecondBrain/90 Library/Programming/Python/Модели и ORM.md
T
2026-06-05 13:29:15 +03:00

283 lines
12 KiB
Markdown

---
status: processing
type: concept
tags:
- django
- backend
- orm
- database
- migrations
created: 2026-05-30
updated: 2026-06-05
source:
- '[[lec1-django_start_models-1.pdf]]'
- '[[lec4-django_lec_queries.pdf]]'
- '[[p1-django-1.pdf]]'
aliases:
- Django Модели и ORM
---
# 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)
```
Если не указать `primary_key=True`, Django автоматически создаёт поле `id`. В современных версиях тип поля по умолчанию обычно задаётся настройкой `DEFAULT_AUTO_FIELD`, часто это `BigAutoField`.
`CharField` требует `max_length`, потому что БД должна знать максимальный размер строки.
`null=True` и `blank=True` — не одно и то же:
- `null=True` разрешает хранить `NULL` в базе данных;
- `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)
```
`related_name` задаёт имя обратной связи. Если у книги есть `author = ForeignKey(Author, related_name="books")`, то у автора можно получить книги так:
```python
author.books.all()
```
Для `ManyToManyField` промежуточная модель задаётся через `through`, когда у самой связи есть дополнительные поля:
```python
class Student(models.Model):
name = models.CharField(max_length=100)
class Course(models.Model):
title = models.CharField(max_length=100)
students = models.ManyToManyField(Student, through="Enrollment")
class Enrollment(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
grade = models.IntegerField(null=True, blank=True)
```
Связь можно менять методами `.add()`, `.remove()`, `.set()`, `.clear()`.
---
## Миграции
Инструмент для синхронизации моделей с БД.
- `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)
```
## Простыми словами для экзамена
Модель Django — это Python-класс, который описывает таблицу БД. Поле модели — это колонка таблицы. Объект модели — это строка таблицы.
`Class Meta` задаёт служебные настройки модели: сортировку, человекочитаемые имена, ограничения, имя таблицы.
Основные методы QuerySet:
- `.all()` — все объекты;
- `.filter()` — несколько объектов по условию;
- `.exclude()` — всё, кроме условия;
- `.get()` — ровно один объект; если ноль или больше одного, будет ошибка;
- `.order_by()` — сортировка;
- `.count()` — количество;
- `.exists()` — есть ли хотя бы одна запись;
- `.distinct()` — убрать дубли;
- `.aggregate()` — один итог по всей выборке, возвращает словарь;
- `.annotate()` — добавляет вычисляемое поле каждому объекту.
Если нужно найти максимум цены по каждой категории, используют `annotate()` на категориях:
```python
Category.objects.annotate(max_price=Max("product__price"))
```
Если нужен один максимум по всем товарам, используют `aggregate()`:
```python
Product.objects.aggregate(max_price=Max("price"))
```