使用 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
信号,我会捕获该信号。我知道 instance
是 Course
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
这里将是一组主键,这些主键参与了 Course
中 enrolled_users
字段的更改。这意味着当操作是 post_remove
时,所有删除的 CustomUser
主键将在 pk_set
kwarg 中传递。
这意味着当 enrolled_users
或 Course
上的更改信号启动时,我们可以检查该操作是否为删除操作。在这种情况下,我们收到的从 enrolled_users
中删除的相同 pk_set
可以直接添加到 previous_enrolled_users
.
我有一个模型 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
信号,我会捕获该信号。我知道 instance
是 Course
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
这里将是一组主键,这些主键参与了 Course
中 enrolled_users
字段的更改。这意味着当操作是 post_remove
时,所有删除的 CustomUser
主键将在 pk_set
kwarg 中传递。
这意味着当 enrolled_users
或 Course
上的更改信号启动时,我们可以检查该操作是否为删除操作。在这种情况下,我们收到的从 enrolled_users
中删除的相同 pk_set
可以直接添加到 previous_enrolled_users
.