Search code examples
python-3.xdjangodjango-modelsmany-to-manydjango-signals

Change instance values after capture change with Django Signals


I have a model Course that have a ManyToMany relation with my CustomUser model:

class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('Email Address'), unique=True)
    user_name = models.CharField(_('User Name'), max_length=150, unique=True)
    # and a lot of other fields and stuff


class Course(models.Model):
    enrolled_users = models.ManyToManyField(CustomUser, related_name="enrolls", blank=True)
    previous_enrolled_users = models.ManyToManyField(CustomUser, related_name="previous_enrolls", blank=True)
    course_name = models.CharField(_("Course Name"), max_length=200)

What I'm trying to implement is that whenever a user finishes a course (and so the user is removed from enrolled_users), my application stores this user in previous_enrolled_users, so I can know the users that were previously enrolled at that course.

I've implement a m2m_changed signal listening like this:

def listen_m2mchange(sender, instance, model, pk_set, action, **kwargs):
    if action == 'pre_remove':
        # I'm trying to guess what to do

m2m_changed.connect(listen_m2mchange, sender=Course.enrolled_users.through)

With that, whenever I remove a user from a course, Django signals the m2m_changed and I capture that signal. I know that instance is the instance of the Course class and that model is the instance of that CustomUser class I'm removing. What I could not guess is how, using the instance of Course class, I can add the CustomUser in the previous_enrolled_users. Any help will be very appreciated.

EDIT 01:

Reading a lot of the docs, I get that what I want is doing this everytime model is removed from enrolled_users:

instance.previous_enrolled_users.add(model)

But when I do it, I get an error:

TypeError: Field 'id' expected a number but got <class 'core.models.CustomUser'>.

Solution

  • Try this:

    def listen_enrolled_users_m2mchange(sender, instance, model, pk_set, action, **kwargs):
        if action == 'post_remove':
            instance.previous_enrolled_users.add(*pk_set)
    
    m2m_changed.connect(listen_enrolled_users_m2mchange, sender=Course.enrolled_users.through)
    

    pk_set here will be a set of primary keys that were involved in the changes of enrolled_users field in Course. That means when the action is post_remove, all the removed CustomUser primary keys will be passed in the pk_set kwarg.

    This then means that when the signal kicks in for the changes on enrolled_users of Course, we can check if the action is a remove. In that case, the same pk_set we received that are removed from enrolled_users can be directly added to previous_enrolled_users.