Как выполнить два оператора обновления в одной транзакции, чтобы они не столкнулись с уникальным ограничением в Django ORM?

#python #django #django-orm

#python #django #django-orm

Вопрос:

Данные модели

 from django.db import models


class RelatedTo(models.Model):
    pass


class Thing(models.Model):
    n = models.IntegerField()
    related_to = models.ForeignKey(RelatedTo, on_delete=models.CASCADE)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['n', 'related_to'],
                name='unique_n_per_related_to'
            )
        ]
 

и

 >>> r = RelatedTo.objects.create()
>>> thing_zero = Thing.objects.create(related_to=r, n=0)
>>> thing_one = Thing.objects.create(related_to=r, n=1)
 

Я хочу переключить их numbers ( n ) .

В update методе моего сериализатора (drf) я пытался

 @transaction.atomic
def update(self, instance, validated_data):
    old_n = instance.n
    new_n = validated_data['n']

    Thing.objects.filter(
        related_to=instance.related_to,
        n=new_n
    ).update(n=old_n)
    return super().update(instance, validated_data)
 

но он все равно сталкивается с ограничением.

select_for_update тоже не помогает.

Возможно ли не столкнуться с этим ограничением БД с помощью Django ORM или мне нужно запускать необработанный sql для достижения этого?

 Django==3.1.2
 
 postgres:12.5
 

Ошибка

 duplicate key value violates unique constraint "unique_n_per_related_to"
DETAIL:  Key (n, related_to)=(1, 1) already exists.
 

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

1. Не знаю, сработает ли это, но попробуйте использовать bulk_update

2. Спасибо за подсказку. Я предполагаю, что это сработает, но кажется странным не вызывать super().update() и использовать bulk_update вместо этого. В любом случае, это единственное решение, о котором я тоже могу подумать.

Ответ №1:

Я не смог решить эту проблему ни с bulk_update помощью, ни с помощью raw sql.

 stmt = f"""
            update {to_update._meta.db_table} as t
            set n = i.n
            from (values
                ('{to_update.id}'::uuid, {n}),
                ('{method.id}'::uuid, {n})
            ) as i(id, n)
            where t.id = i.id
            """

with connection.cursor() as cur:
    cur.execute(stmt)
 

Единственное решение этой проблемы — сделать столбец обнуляемым и записать 3 раза в таблицу, что причиняет физическую боль.