Home > Mobile >  Is there a way to save a Django object and update other objects without a recursion loop?
Is there a way to save a Django object and update other objects without a recursion loop?

Time:07-12

I have a Django model:

class Event(models.Model):
   fk = models.ForeignKey(Foreign, null=True, on_delete=models.SET_NULL)
   display = display = models.BooleanField(default=True)
   ...

I'd like to override the save method to turn off display for other events that share the fk value. However, I keep reaching infinite recursion loops because if I override the save method and then save the other objects, it keeps calling the function. Is there a way to only run the save method on the first object that's saved and not keep creating recursive instances?

CodePudding user response:

You can work .update(…) [Django-doc] a QuerySet of other objects, with:

from django.db.models import Q

class Event(models.Model):
    fk = models.ForeignKey(Foreign, null=True, on_delete=models.SET_NULL)
    display = models.BooleanField(default=True)
    # …

    def save(self, *args, **kwargs):
        if self.fk_id is not None and self.display:
            Event.objects.filter(
                ~Q(pk=self.pk), fk_id=self.fk_id
            ).update(display=False)
        return super().save(*args, **kwargs)

But this also immediately shows that there are ways to circumvent the .save(…) method [Django-doc] on a model, therefore just overriding .save(…) will not be sufficient, since there are ORM methods that circumvent the .save(…) and pre-save and post-save signals (which are triggered by the the .save(…) method).

You can also add a constraint to enforce that there is at most one Event for each fk that has display=True:

from django.db.models import Q

class Event(models.Model):
    fk = models.ForeignKey(Foreign, null=True, on_delete=models.SET_NULL)
    display = models.BooleanField(default=True)
    # …

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=('fk',),
                condition=Q(display=True),
                name='only_one_display_per_fk'
            )
        ]
  • Related