Изменение значений экземпляра после изменения захвата с помощью Django Signals

У меня есть модель Course, которая имеет отношение ManyToMany с моей моделью CustomUser:

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)

Я пытаюсь реализовать следующее: когда пользователь заканчивает курс (и таким образом пользователь удаляется из enrolled_users), мое приложение сохраняет этого пользователя в previous_enrolled_users, чтобы я мог узнать пользователей, которые ранее были записаны на этот курс.

Я реализовал прослушивание сигнала m2m_changed следующим образом:

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)

Таким образом, всякий раз, когда я удаляю пользователя из курса, Django сигнализирует m2m_changed, и я перехватываю этот сигнал. Я знаю, что instance является экземпляром класса Course и что model является экземпляром того класса CustomUser, который я удаляю. Я не могу понять, как, используя экземпляр класса Course, я могу добавить CustomUser в previous_enrolled_users. Любая помощь будет очень признательна.

вот models.py, который я использовал для воссоздания проблемы:

from django.contrib.auth.base_user import BaseUserManager
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import m2m_changed


class CustomUserManager(BaseUserManager):
    """
    Custom user model manager where username is unique identifiers
    able to add more fields to Django basic User model.
    """

    def create_user(self, username, password, **extra_fields):
        """
        Create and save a User with the given email and password.
        """
        if not username:
            raise ValueError(_('The username must be set'))
        user = self.model(username=username, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, username, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError(_('Superuser must have is_staff=True.'))
        if extra_fields.get('is_superuser') is not True:
            raise ValueError(_('Superuser must have is_superuser=True.'))
        return self.create_user(username, password, **extra_fields)

    def create_staffuser(self, username, first_name, last_name,  password=None):
        if not username:
            raise ValueError("User must have an email")
        if not password:
            raise ValueError("User must have a password")
        if not first_name:
            raise ValueError("User must have a first name")
        if not last_name:
            raise ValueError("User must have a last name")

        user = self.model(
            email=self.normalize_email(username)
        )
        user.first_name = first_name
        user.last_name = last_name
        user.set_password(password)  # change password to hash
        user.is_admin = False
        user.is_staff = True
        user.save(using=self._db)
        return user


class CustomUser(AbstractUser):
    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
    objects = CustomUserManager()


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


def listen_m2mchange(sender, **kwargs):
    pass

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

полезный код в оболочке Django:


In [1]: from hello.models import CustomUser, Course

In [2]: U3 = CustomUser.objects.create(username='test3', user_name='tes 
   ...: t3', email='test3@g.com')

In [3]: U3 = CustomUser.objects.create(username='test4', user_name='tes 
   ...: t4', email='test4@g.com')
In [4]: C = Course.objects.create(course_name='some course3')
In [5]: C.enrolled_users.add(U3)
In [6]: C.enrolled_users.get()
Out[6]: <CustomUser: test4>
In [7]: C.enrolled_users.remove(U3) # removes the user from the set

Надеюсь, это было полезно.

Попробуйте это:

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 здесь будет набор первичных ключей, которые участвовали в изменении поля enrolled_users в Course. То есть, когда действие будет выполнено post_remove, все удаленные CustomUser первичные ключи будут переданы в pk_set kwarg.

Это означает, что когда поступает сигнал об изменениях на enrolled_users из Course, мы можем проверить, является ли это действие удалением. В этом случае, те же самые pk_set, которые мы получили и которые удалены из enrolled_users, могут быть непосредственно добавлены в previous_enrolled_users.

Вернуться на верх