Есть ли идиоматический способ получить или создать, а затем обновить объект в Django?


У меня есть модель Django под названием StaffSettings, которая содержит различные параметры конфигурации для пользователей в моем приложении Django. Каждый пользователь имеет не более одной записи в таблице StaffSettings.

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

def set_default_year_level(u, year_level):
    obj, _created = StaffSettings.objects.get_or_create(user=u)
    obj.default_year_level = year_level
    obj.save()

Я бы предпочел, чтобы тело функции помещалось в одну строку, потому что это похоже на общий случай использования, но если бы я определил его как

def set_default_year_level(u, year_level):
    StaffSettings.objects.filter(user=u).update(default_year_level=year_level)

Который прекрасно работает, если пользователь, о котором идет речь, уже имеет строка в таблице StaffSettings, но она не создаст соответствующую строку, если она не существует.

Каков идиоматический / лучший способ кодирования этого? (например, существует ли какая-то функция filter_or_create? Или другие люди пишут декораторы / вспомогательные функции для обработки этой идиомы?)

2 7

2 ответа:

Я не вижу никаких проблем с вашей первой функцией, я бы написал то же самое для этого usecase.

Однако если вам нужна одна и та же функция на большом количестве полей в вашей модели, и вы не хотите повторяться, вы можете передать поле в качестве параметра:

def set_default_value(u, field, value):
  obj, _created = StaffSettings.objects.get_or_create(user=u)
  setattr(obj, field, value)
  obj.save()

И я буду держаться подальше от функции update(), так как эта функция предназначена для обновления нескольких объектов одновременно и не вызывает метод save() и сигналы на ваших моделях (см. https://docs.djangoproject.com/en/dev/topics/db/queries/#updating-multiple-objects-at-once)

Начиная с Django 1.7 вы можете использовать update_or_create():

def set_default_year_level(u, year_level):
    obj, _created = StaffSettings.objects.update_or_create(
        user=u,
        default_year_level=year_level
    )

Или, для более общего случая, как объяснено в предыдущем ответе:

def set_default_values(u, **kwargs):
    obj, _created = StaffSettings.objects.update_or_create(user=u, defaults=kwargs)

Который также достигает вашего дополнительного требования

Я бы предпочел, чтобы тело функции помещалось в одну строку