🐛 Типичные ошибки: Django ORM запросы

К оглавлению урока

⚡ Топ ошибок

  • DoesNotExistget() не нашёл запись → перейти на filter().first() или try/except
  • MultipleObjectsReturnedget() нашёл >1 запись → уточнить условие
  • FieldError — опечатка в lookup (titl__icontains) → проверить имя поля
  • Нет __ — написали filter(titleicontains=...) вместо title__icontains
  • N+1 проблема — обращение к FK в цикле → использовать select_related()

1. DoesNotExist — запись не найдена

Ошибка

# Если книги с таким названием нет — упадёт с исключением
book = Book.objects.get(title="Несуществующая книга")
# → Book.DoesNotExist: Book matching query does not exist.

Решение

# Вариант 1 — try/except
try:
    book = Book.objects.get(title="Несуществующая книга")
except Book.DoesNotExist:
    book = None

# Вариант 2 — filter().first() (None если не найдено)
book = Book.objects.filter(title="Несуществующая книга").first()

# Вариант 3 — get_or_create()
book, created = Book.objects.get_or_create(
    title="Новая книга",
    defaults={"author": "Unknown", "published_date": "2024-01-01"}
)

2. MultipleObjectsReturned — несколько записей

Ошибка

# Если несколько книг с author="Orwell" — ошибка
book = Book.objects.get(author="Orwell")
# → Book.MultipleObjectsReturned

Решение

# Уточнить условие по уникальному полю
book = Book.objects.get(title="1984", author="Orwell")

# Или — если нужна одна, используйте filter()
book = Book.objects.filter(author="Orwell").first()

# Или — обработать все
books = Book.objects.filter(author="Orwell")
for book in books:
    print(book.title)

3. FieldError — несуществующее поле

Ошибка

# Опечатка в имени поля
books = Book.objects.filter(titl__icontains="gatsby")
# → FieldError: Cannot resolve keyword 'titl' into field.
# Choices are: author, id, is_bestseller, price, published_date, title

Решение

# Правильное имя поля
books = Book.objects.filter(title__icontains="gatsby")

# Если не помните поля — проверьте модель
print([f.name for f in Book._meta.get_fields()])

4. Забыли двойное подчёркивание __

Ошибка

# Одно подчёркивание — Django воспримет как имя поля
books = Book.objects.filter(title_icontains="gatsby")
# → FieldError: Cannot resolve keyword 'title_icontains'

Решение

# Два подчёркивания — разделитель поле__lookup
books = Book.objects.filter(title__icontains="gatsby")

5. Неправильный тип значения в lookup

Ошибка

# isnull ожидает True/False, не строку
books = Book.objects.filter(price__isnull="true")
# Работает, но "true" как строка — не то, что ожидается

Решение

# Использовать булевые значения
books = Book.objects.filter(price__isnull=True)
books = Book.objects.filter(price__isnull=False)

6. N+1 проблема при обращении к связанным объектам

Ошибка

# Если Author — отдельная модель через FK
# Каждая итерация делает отдельный SQL-запрос
books = Book.objects.all()
for book in books:
    print(book.author_obj.name)  # N отдельных запросов SELECT author

Решение

# select_related() — JOIN в одном запросе (FK / OneToOne)
books = Book.objects.select_related('author_obj').all()
for book in books:
    print(book.author_obj.name)  # данные уже загружены

# prefetch_related() — для ManyToMany или обратных FK
books = Book.objects.prefetch_related('tags').all()
⚠️ Проверить по документации: select_related() и prefetch_related() — ключевые инструменты оптимизации ORM. Подробнее в уроках по advanced ORM.

7. Использование filter() как get() без проверки количества

Ошибка

# filter() возвращает QuerySet, а не объект
book = Book.objects.filter(title="1984")
print(book.title)  # AttributeError: 'QuerySet' object has no attribute 'title'

Решение

# Если нужна одна запись — get() или filter().first()
book = Book.objects.get(title="1984")  # ровно одна
# или
book = Book.objects.filter(title="1984").first()  # первая или None
print(book.title)

8. Изменение объекта без save()

Ошибка

# Изменили атрибут, но не вызвали save() — изменение не сохранится в БД
book = Book.objects.get(id=1)
book.title = "Новое название"
# Забыли book.save() — в БД всё по-прежнему

Решение

book = Book.objects.get(id=1)
book.title = "Новое название"
book.save()  # или book.save(update_fields=['title']) — экономнее

# Массовое обновление через QuerySet.update() — save() не нужен
Book.objects.filter(id=1).update(title="Новое название")