class Badge(SafeDeleteModel):
owner = models.ForeignKey(settings.AUTH_USER_MODEL,
blank=True, null=True,
on_delete=models.PROTECT)
restaurants = models.ManyToManyField(Restaurant)
identifier = models.CharField(max_length=2048) # not unique at a DB level!
I want to ensure that for any badge, for a given restaurant, it must have a unique identifier. Here are the 4 ideas I have had:
- idea #1: using
unique_together
-> Does not work with M2M fields as explained [in documentation] (https://docs.djangoproject.com/en/2.1/ref/models/options/#unique-together) - idea #2: overriding
save()
method. Does not fully work with M2M, because when callingadd
orremove
method,save()
is not called. idea #3: using an explicite
through
model, but since I'm live in production, I'd like to avoid taking risks on migrating important structures like theses. EDIT: after thinking of it, I don't see how it could help actually.idea #4: Using a
m2m_changed
signal to check the uniqueness anytime theadd()
method is called.
I ended up with the idea 4 and thought everything was OK, with this signal...
@receiver(m2m_changed, sender=Badge.restaurants.through)
def check_uniqueness(sender, **kwargs):
badge = kwargs.get('instance', None)
action = kwargs.get('action', None)
restaurant_pks = kwargs.get('pk_set', None)
if action == 'pre_add':
for restaurant_pk in restaurant_pks:
if Badge.objects.filter(identifier=badge.identifier).filter(restaurants=restaurant_pk):
raise BadgeNotUnique(MSG_BADGE_NOT_UNIQUE.format(
identifier=badge.identifier,
restaurant=Restaurant.objects.get(pk=restaurant_pk)
))
...until today when I found in my database lots of badges with the same identifier but no restaurant (should not happend at the business level)
I understood there is no atomicity between the save()
and the signal.
Which means, if the user have an error about uniqueness when trying to create a badge, the badge is created but without restaurants linked to it.
So, the question is: how do you ensure at the model level that if the signal raises an Error, the save()
is not commited?
Thanks!
IdentifiedBadge
with a m2m to restaurant. In such way you can guarantee this by design. Usually it is better to enforce things by design then aiming to patch these. Signals,.save()
, etc. can be bypassed (for example when updating in bulk). – Judgemade