rsinger86 / django-lifecycle

Declarative model lifecycle hooks, an alternative to Signals.
https://rsinger86.github.io/django-lifecycle/
MIT License
1.36k stars 89 forks source link

Many to many fields #120

Closed danialb007 closed 1 year ago

danialb007 commented 1 year ago

Greetings. I've been working on a project and i wanted to fire a hook after an object is created and immediately use its m2m field's value and process some stuff. AFTER_CREATE and AFTER_SAVE hooks are not working as expected in this case. (even with on_commit=True) so if field (X) is a ManyToManyField you would write code like this:

@hook(AFTER_CREATE)
    def my_hook(self):
        print(self.X.all())

and this results in an empty queryset.

Django has a built-in signal called m2m_changed and it works as i expected but the syntax is messy and thats the main reason i preferred this package.

EnriqueSoria commented 1 year ago

tl;dr

AFAIK this is not possible with django-lifecycle, for this use case stick to the signal or create a function that handle the whole behaviour (creating object, adding m2m and doing the "after create" thing.

Explanation

A m2m relation what it does is creating another table that contains a foreing key to both tables:

class Person(models.Model):
    ...

class Group(models.Model):
    members = models.ManyToManyField(Person, through='Membership')

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)

(If you don't specify this through model explicitly, it's created automatically) So... in order to add a Membership you need before to have a Person and a Group saved (because you need their ids)

So, if we add a hook to either side of the M2M, it will be called before the through instance are created:

class Person(LifecycleModel):

    @hook(AFTER_CREATE)
    def my_hook(self):
        print(self.X.all())

class Group(models.Model):
    members = models.ManyToManyField(Person, through='Membership')

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)

When you add or remove items for a M2M relationship, the save method is not called, but signals do.

danialb007 commented 1 year ago

Thanks. Actually makes sense and i kind of knew that, just thought it's somehow possible and i didn't know. Closing this issue.