使用 Django Signals 捕获更改后更改实例值

Change instance values after capture change with Django Signals

我有一个模型 Course 与我的 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 信号,我会捕获该信号。我知道 instanceCourse class 的实例,而 model 是我要删除的 CustomUser class 的实例。我无法猜到的是如何使用 Course class 的实例,我可以在 previous_enrolled_users 中添加 CustomUser。任何帮助将不胜感激。

编辑 01:

阅读了很多文档,我明白我想要的是每次从 enrolled_users 中删除 model 时执行此操作:

instance.previous_enrolled_users.add(model)

但是当我这样做的时候,我得到一个错误:

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

这是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 中的有用代码 shell:


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 这里将是一组主键,这些主键参与了 Courseenrolled_users 字段的更改。这意味着当操作是 post_remove 时,所有删除的 CustomUser 主键将在 pk_set kwarg 中传递。

这意味着当 enrolled_usersCourse 上的更改信号启动时,我们可以检查该操作是否为删除操作。在这种情况下,我们收到的从 enrolled_users 中删除的相同 pk_set 可以直接添加到 previous_enrolled_users.