Как следует обрабатывать возможное состояние гонки в методе save()
модели?
Например, в следующем примере реализована модель с упорядоченным списком связанных элементов.При создании нового элемента в качестве его позиции используется текущий размер списка.
Из того, что я могу сказать, это может пойти не так, если несколько элементов создаются одновременно.
class OrderedList(models.Model):
# ....
@property
def item_count(self):
return self.item_set.count()
class Item(models.Model):
# ...
name = models.CharField(max_length=100)
parent = models.ForeignKey(OrderedList)
position = models.IntegerField()
class Meta:
unique_together = (('parent','position'), ('parent', 'name'))
def save(self, *args, **kwargs):
if not self.id:
# use item count as next position number
self.position = parent.item_count
super(Item, self).save(*args, **kwargs)
Я встречал @ транзакций .commit_on_success()
, но этокажется, относится только к представлениям.Даже если бы это применимо к модельным методам, я все равно не знал бы, как правильно обработать неудачную транзакцию.
Я сейчас так обрабатываю, но это больше похоже на взлом, чем на решение
def save(self, *args, **kwargs):
while not self.id:
try:
self.position = self.parent.item_count
super(Item, self).save(*args, **kwargs)
except IntegrityError:
# chill out, then try again
time.sleep(0.5)
Есть предложения?
Обновление:
Другая проблема с вышеприведенным решением состоит в том, что цикл while
никогда не завершится, если IntegrityError
вызван name
конфликт (или любое другое уникальное поле в этом отношении).
Для справки, вот что у меня есть, что, кажется, делает то, что мне нужно:
def save(self, *args, **kwargs):
# for object update, do the usual save
if self.id:
super(Step, self).save(*args, **kwargs)
return
# for object creation, assign a unique position
while not self.id:
try:
self.position = self.parent.item_count
super(Step, self).save(*args, **kwargs)
except IntegrityError:
try:
rival = self.parent.item_set.get(position=self.position)
except ObjectDoesNotExist: # not a conflict on "position"
raise IntegrityError
else:
sleep(random.uniform(0.5, 1)) # chill out, then try again