I have the following model which represents an instance of a model that is logically a child of another model, with the ordering of the children being significant.
class OrderedChild(models.Model):
parent = models.ForeignKey(Parent)
ordering = models.PositiveIntegerField()
class Meta:
ordering = ["parent_id", "ordering"]
constraints = [
models.UniqueConstraint(
fields=["parent_id, "ordering"],
name="same_parent_unique_ordering",
),
]
In order to get the correct value of the ordering
, I do the following:
# inside OrderedChild class
def save(self, *args, **kwargs):
if self.pk is None:
self.ordering = self.get_ordering_position()
def get_ordering_position(self):
siblings = self.get_siblings()
max_ordering = siblings.aggregate(max_ordering=Max("ordering"))["max_ordering"]
return max_ordering + 1 if max_ordering is not None else 0
def get_siblings(self):
return OrderedChild.objects.filter(parent=self.parent)
This works fine most of the time, but I’ve seen a few IntegrityError
s here and there, where the unique constraint was violated.
My understanding is that the constraint might be violated if two very close requests come to create instances of OrderedChild with the same parent. During creation, when neither of the two has been created yet, get_ordering_position
might give the same result, causing the integrity error.
Wrapping this inside of a transaction wouldn’t help this specific issue: I need some sort of lock. I know there is a select_for_update
method, but I’m not sure it’s the right answer in my case.
How would you approach the issue?