Django - обмен внешними ключами в отношениях один-к-одному в атомарной транзакции

у меня есть модель Unit с обработчиком, связанным один к одному, определенным следующим образом:

handler = models.OneToOneField('users.Handler', related_name="unit_obj", blank=True, null=True, on_delete=models.SET_NULL)

я пытаюсь обновить набор единиц в атомарной транзакции, где внешний ключ единицы handler_id может быть установлен на null или любой другой handler_id. проблема в том, что обработчики меняются местами

я сначала попробовал сохранить обновленный блок внутри атомарной транзакции. но как только вызывается unit.save() на первом блоке, я получаю ошибку целостности, потому что второй блок все еще имеет тот же handler_id. я надеялся, что атомарная транзакция отложит ошибку целостности на отдельные вызовы unit.save() и выбросит ошибку в конце транзакции, если она действительно была.

try:
    with transaction.atomic():
        for unit_model in unit_models:
            unit = Unit.objects.get(id=unit_model['id'])
            unit.handler_id = unit_model['handler']['id'] if unit_model['handler'] is not None else None
            unit.save()
except IntegrityError as e:
    print(f'Error saving units: {str(e)}')

Затем я попробовал использовать bulk_update, что, как я был уверен, решит проблему. К сожалению, я получаю ту же ошибку (дублированное значение ключа нарушает уникальное ограничение), хотя я подтвердил, что в списке нет дубликатов. Кажется, что сохранения применяются последовательно, хотя это не то, чего я ожидал.

try:
    with transaction.atomic():
        bulk_units = []
        for unit_model in unit_models:
            unit = Unit.objects.get(id=unit_model['id'])
            unit.handler_id = unit_model['handler']['id'] if unit_model['handler'] is not None else None                
            bulk_units.append(unit)
        Unit.objects.bulk_update(bulk_units, ['handler_id'])
except IntegrityError as e:
    print(f'Error saving units: {str(e)}')
На странице

django constraint page предлагается, что ограничения могут быть deferrable, что, очевидно, откладывает их применение до конца транзакции. Это звучало замечательно, пока я не обнаружил, что OneToOneField не раскрывает никаких параметров для deferrable и есть страница feature-request page, где предлагается, что это слишком нишево, чтобы стоило делать.

без этого единственным способом атомарного обмена будет сначала установить все поля handler_id блока в None, а затем пройтись по списку и установить их в обновленные значения. но я что-то упускаю или понимаю неправильно? я не вижу причин, по которым атомарный обмен уникальными значениями должен быть настолько сложным.

Это ограничение, на данный момент, вы можете просто добавить еще 1 запрос, чтобы обойти его, установив все в None перед заменой

unit_models.update(handler=None)

Или вы можете использовать пользовательскую миграцию SQL для изменения уникального ограничения на откладываемое.

ALTER TABLE package_unit ALTER CONSTRAINT unique_constraint DEFERRABLE INITIALLY DEFERRED
Вернуться на верх