Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

post_save signal and relations

I am applying the post_save signal to apply user rights per object, and then filter the queryset accordingly.

My model is like this:

class Project(models.Model):
    # Relations with other entities.
    employees = models.ManyToManyField('staff.Person', through='project.PersonProjectMembership',
                                       related_name='projects')
    research_groups = models.ManyToManyField('group.Group', related_name='projects',
                                             through='project.ProjectGroupMembership')
    departments = models.ManyToManyField('department.Department', related_name='projects',
                                         through='project.ProjectDepartmentMembership')

The problem is: when I catch the post-save signal, although I have entered values for departments, research_groups & employees, they always seem to be empty. Is there anything that I have missed?

Update: Below the current code, which is not yet working as expected. I have changed the post_save by m2m_changed.

from django.db.models.signals import m2m_changed
from django.db import models
from django.dispatch.dispatcher import receiver

class Project(models.Model):
    employees = models.ManyToManyField('staff.Person', through='project.PersonProjectMembership',
    related_name='projects')

class PersonProjectMembership(models.Model):
    project = models.ForeignKey('project.Project', related_name="person_memberships")
    person = models.ForeignKey('staff.Person', related_name="project_memberships")
    lead = models.BooleanField(default=False)
    position = models.CharField(max_length=50)
    project_manager = models.BooleanField(
        default=False
    )

    class Meta:
        permissions = (
            ('view_personprojectmembership', _('View person project membership')),
        )

@receiver(m2m_changed, sender=Project.employees.through)
def _on_save_project_assign_privileges(sender, instance, action, reverse, model, pk_set, using, **kwargs):
    # [...]

SOLUTION

In my Project model, I am explicitly defining PersonProjectMembership as intermediate model in the m2m relation employees:

class Project(models.Model):
    # Relations with other entities.
    employees = models.ManyToManyField('staff.Person', through='project.PersonProjectMembership', related_name='projects')

The timeline when I save a project, is as follows:

  1. Project.save()
  2. PersonProjectMembership.save()

So it's normal that, on Project.post_save employees is still empty. What I had to do was listening to the PersonProjectMembership post_save signal:

@receiver(post_save, sender=PersonProjectMembership)
def my_listener(**kwargs):
    # do stuff [...]

Look into https://docs.djangoproject.com/en/1.9/ref/models/fields/#django.db.models.ManyToManyField.through and https://docs.djangoproject.com/en/1.9/topics/signals/

like image 367
sogeking Avatar asked Oct 22 '22 07:10

sogeking


2 Answers

After digging a lot on my code, and doing simple tests, I tried the m2m_changed signal as krasnoperov suggested. I realised that this signal does not work well if you explicitly declare a "through" model, "PersonProjectMembership" in my case.

Then I thought again and linked my method to the post_save signal of "PersonProjectMembership". That works perfectly.

like image 167
sogeking Avatar answered Oct 27 '22 22:10

sogeking


As you might know, Many2Many relations are stored through additional table, which contains Primary Keys from both ends of relation. Because of that, saving of model instance with Many2Many relation it is two steps process:

  1. At first, instance is save: new record in database is created and instance receives it's Primary Key. post_save is fired at this moment.

  2. After that, relations are saved: records in relations table are created. m2m-changed signal is fired at this moment.

In other words, when post_save is fired, m2m relations are not handled yet.

You can check the documentation: m2m-changed

like image 40
krasnoperov Avatar answered Oct 27 '22 20:10

krasnoperov