Django ORM

Создание объектов Django ORM | Курс по Django ORM урок 2.1

Создание объектов Django ORM | Курс по Django ORM урок 2.1
Mikhail
Автор
Mikhail
Опубликовано 16.03.2026
0,0
Views 11

Цель урока

Разобрать все способы создания объектов в Django ORM: save(), create(), get_or_create(), update_or_create(). Понять, чем они отличаются на уровне SQL, сколько запросов каждый делает и в каких ситуациях что выбирать.

Необходимые знания

  • Уроки 1.1 - 1.4: модели, поля, миграции
  • Базовое понимание INSERT в SQL

save() - базовый способ

Самый явный способ создать объект, создать экземпляр модели и вызвать save().

from shop.models import Category, Product
from decimal import Decimal

# Создаем объект в памяти, никаких запросов к БД
category = Category.objects.get(slug="phones")

product = Product(
    name="OnePlus 12",
    slug="oneplus-12",
    price=Decimal("699.99"),
    stock=50,
    category=category,
)

# Только здесь идет запрос к БД
product.save()

SQL при save() на новом объекте:

INSERT INTO shop_product
    (name, slug, description, price, stock, is_active, created_at, updated_at, category_id)
VALUES
    ('OnePlus 12', 'oneplus-12', '', 699.99, 50, true, NOW(), NOW(), 3)
RETURNING id;

Django использует RETURNING id (возможности PostgreSQL), чтобы получить сгенерированный первичный ключ без второго запроса. После save() атрибут product.id содержит реальный id из БД.

save() на существующем объекте - UPDATE

Если объект уже имеет pk, save() делает UPDATE:

product = Product.objects.get(slug="oneplus-12")
product.price = Decimal("649.99")
product.save()
UPDATE shop_product
SET name = 'OnePlus 12',
    slug = 'oneplus-12',
    description = '',
    price = 649.99,
    stock = 50,
    is_active = true,
    updated_at = NOW(),
    category_id = 3
WHERE id = 42;

Обратите внимание: Django обновляет все поля, даже те, которые не менялись. Это расточительно если у модели много полей. Чтобы обновить только конкретные поля:

product.price = Decimal("649.99")
product.save(update_fields=["price", "updated_at"])
UPDATE shop_product
SET price = 649.99, updated_at = NOW()
WHERE id = 42;

Если вы знаете что изменили, всегда указывайте update_fields.

Количество запросов

product = Product(name="...", ...)  # 0 запросов
product.save()                      # 1 запрос (INSERT)

create() - создание за один шаг

create(), это Product(**kwargs) + save() в одном вызове:

product = Product.objects.create(
    name="OnePlus 12",
    slug="oneplus-12",
    price=Decimal("699.99"),
    stock=50,
    category=category,
)

SQL идентичен save(), один INSERT. Разница только в синтаксисе Python.

create() возвращает созданный объект с заполненным pk. Используйте его когда не нужно устанавливать значения атрибутов поэтапно.

Количество запросов

product = Product.objects.create(...)  # 1 запрос (INSERT)

get_or_create() - получить или создать

Решает задачу: "получите объект с такими параметрами, если его нет, создайте". Атомарная операция с точки зрения Django, один или два запроса, но без race condition при правильном использовании.

category, created = Category.objects.get_or_create(
    slug="smartphones",           # поля для поиска (WHERE)
    defaults={"name": "Smartphones"},  # поля только для создания
)

if created:
    print(f"Создана новая категория: {category.name}")
else:
    print(f"Найдена существующая категория: {category.name}")

SQL когда объект существует:

SELECT * FROM shop_category WHERE slug = 'smartphones' LIMIT 1;
-- объект найден, возвращает (category, False)

SQL когда объекта нет:

SELECT * FROM shop_category WHERE slug = 'smartphones' LIMIT 1;
-- не найдено
INSERT INTO shop_category (slug, name, parent_id)
VALUES ('smartphones', 'Smartphones', NULL)
RETURNING id;
-- возвращает (category, True)

Итого 1 или 2 запроса.

defaults - поля только для создания

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

# Ищем по slug. Если найдем, name из defaults НЕ применяется к найденному объекту.
# Если не найдем, то создаем с name="Smartphones"
category, created = Category.objects.get_or_create(
    slug="smartphones",
    defaults={"name": "Smartphones"},
)

Если нужно и создать, и обновить, используйте update_or_create.

Race condition в get_or_create

get_or_create не полностью защищен от гонки в высоконагруженных системах. Два процесса одновременно могут не найти объект и оба попытаются сделать INSERT. Первый успеет, второй получит ошибку уникальности.

Django обрабатывает это при IntegrityError во время INSERT он делает повторный SELECT. Но это работает только если поля в поиске покрыты unique или unique_together. Убедитесь, что поля поиска имеют уникальное ограничение в БД.

# slug - unique, поэтому get_or_create безопасен
Category.objects.get_or_create(slug="smartphones", defaults={...})

# name не unique, get_or_create может создать дубли или упасть
Category.objects.get_or_create(name="Smartphones", defaults={...})  # опасно

Количество запросов

obj, created = Model.objects.get_or_create(...)
# Если объект есть: 1 SELECT
# Если объекта нет: 1 SELECT + 1 INSERT

update_or_create() - обновить или создать

Работает как get_or_create, но если объект найден, обновляет его поля из defaults.

from decimal import Decimal

product, created = Product.objects.update_or_create(
    slug="oneplus-12",              # поля для поиска
    defaults={                      # поля для создания ИЛИ обновления
        "name": "OnePlus 12",
        "price": Decimal("699.99"),
        "stock": 50,
        "category": category,
    },
)

SQL когда объект существует:

SELECT * FROM shop_product WHERE slug = 'oneplus-12' LIMIT 1;
UPDATE shop_product
SET name = 'OnePlus 12', price = 699.99, stock = 50, updated_at = NOW()
WHERE id = 42;

SQL когда объекта нет:

SELECT * FROM shop_product WHERE slug = 'oneplus-12' LIMIT 1;
INSERT INTO shop_product (slug, name, price, stock, ...) VALUES (...) RETURNING id;

Итого: 1 SELECT + 1 UPDATE или 1 SELECT + 1 INSERT = всегда 2 запроса.

create_defaults - разные данные для создания и обновления (Django 5.0+)

До Django 5.0 нельзя было задать разные значения для создания и обновления. Django 5.0 добавил create_defaults:

product, created = Product.objects.update_or_create(
    slug="oneplus-12",
    defaults={                  # применяется при UPDATE
        "price": Decimal("699.99"),
        "stock": 50,
    },
    create_defaults={           # применяется при INSERT (объединяется с defaults)
        "name": "OnePlus 12",
        "category": category,
        "description": "Flagship smartphone",
    },
)

При создании используются defaults + create_defaults. При обновлении, только defaults. Это полезно когда некоторые поля нужно задать при создании, но не перезаписывать при обновлении.

Model.NotUpdated (Django 6.0+)

В Django 6.0 появилось исключение Model.NotUpdated, поднимается когда save(force_update=True) не нашел строку для обновления (затронуто 0 строк). Раньше возникал DatabaseError.

try:
    product.save(force_update=True)
except Product.NotUpdated:
    # объект был удален параллельным процессом
    handle_missing_product()

Количество запросов

obj, created = Model.objects.update_or_create(...)
# Всегда: 1 SELECT + 1 INSERT или 1 SELECT + 1 UPDATE = 2 запроса

Сравнение методов

Метод Запросов Когда использовать
Model() + save() 1 (INSERT) Когда нужно поэтапно заполнять поля
create() 1 (INSERT) Создать объект за один вызов
get_or_create() 1-2 Идемпотентное создание, поле - unique
update_or_create() 2 Синхронизация данных из внешнего источника

Сигналы и save()

save() и create() вызывают сигналы pre_save и post_save. Это важно если в проекте есть обработчики сигналов, они выполнятся при каждом save(). Если есть такой обработчик:

@receiver(post_save, sender=Product)
def update_search_index(sender, instance, created, **kwargs):
    if created:
        search_index.add(instance)

То Product.objects.create(...) вызовет update_search_index. А bulk_create(...) по умолчанию НЕ вызовет, разберем в уроке 2.5.


Валидация при создании

save() и create() не вызывают full_clean() автоматически. Валидация Django (validators, clean()) не выполняется при прямом сохранении через ORM.

# Это НЕ вызовет ошибку, даже если rating=10 нарушает логику
Review.objects.create(product=product, user=user, rating=10, text="")
# Но CheckConstraint в БД не пропустит и PostgreSQL вернет ошибку

Если нужна валидация на уровне Django, вызывайте явно:

review = Review(product=product, user=user, rating=10)
review.full_clean()  # вызовет все валидаторы и clean()
review.save()

Constraints на уровне БД (урок 6.3) работают всегда, независимо от того, через ORM пришли данные или нет.


Практическое задание

  1. Создайте новый продукт Product() + save(). В логировании SQL в консоли убедитесь, что выполнился ровно один INSERT с RETURNING.

  2. Измените цену созданного продукта и сохраните с помощью save(update_fields=["price", "updated_at"]). Сравните SQL с обычным save(), сколько полей в SET?

  3. Используйте get_or_create для создания категории "Accessories". Вызовите дважды с одинаковыми параметрами. Убедитесь что второй вызов не создает дубль и возвращает created=False.

  4. Используйте update_or_create для синхронизации продукта, если продукт с таким slug существует, обнови цену и stock, если нет, создайте с полным набором полей. Проверьте оба сценария.


Возможные ошибки

save() без update_fields при частичном обновлении

product = Product.objects.get(id=1)
product.price = Decimal("499.99")
product.save()
# Обновляет ВСЕ поля, включая updated_at, name, stock...
# Если параллельный процесс изменил stock, изменение будет потеряно

# Правильно:
product.save(update_fields=["price", "updated_at"])

Поиск в get_or_create по не уникальному полю

# name не уникален и может создать дубли
Category.objects.get_or_create(name="Electronics")

# get() внутри get_or_create вызовет MultipleObjectsReturned если найдет несколько

Игнорирование возвращаемого created

# Теряем информацию, создан объект или найден
category = Category.objects.get_or_create(slug="phones")[0]

# Лучше всегда распаковывать
category, created = Category.objects.get_or_create(slug="phones")

Передача объекта FK туда, где ждут id

# Оба варианта работают, но второй на один атрибут эффективнее
Product.objects.create(category=category)       # Django сам возьмет category.id
Product.objects.create(category_id=category.id) # чуть явнее, тот же SQL

Связь со следующим уроком

В уроке 2.2 переходим к чтению данных, QuerySet API. Разберем, что такое QuerySet и почему он ленивый, какие методы возвращают QuerySet, а какие немедленно выполняют запрос. И как values(), only(), defer() влияют на то, какие колонки попадают в SELECT.


<< Урок 1.4

Урок 2.2 >>


Подписывайтесь на мой Telegram канал

Если вам нужен ментор и вы хотите научиться веб-разработке, узнать подробнее

Авторизуйтесь, чтобы оставить комментарий.

Комментариев: 0

Нет комментариев.

Тут может быть ваша реклама

Пишите info@aisferaic.ru

Похожие туториалы