Skip to main content
 首页 » 编程设计

python之Django : Programmatically add Groups on User save

2025年05月04日34myhome

保存用户后,我需要确保其实例默认与组相关联。

我找到了两种实现方式:

  • 覆盖模型的save() 方法

    模型.py:

    from django.contrib.auth.models import AbstractUser, Group 
     
     
    class Person(AbstractUser): 
     
        def save(self, *args, **kwargs): 
            super().save(*args, **kwargs) 
            to_add = Group.objects.get(id=1)  # get_or_create is a better option 
            instance.groups.add(to_add) 
    
  • 捕获 post_save 信号:

    信号.py:

    from django.conf import settings 
    from django.contrib.auth.models import Group 
    from django.db.models.signals import post_save 
    from django.dispatch import receiver 
     
     
    @receiver( 
        post_save, 
        sender=settings.AUTH_USER_MODEL, 
    ) 
    def save_the_group(instance, raw, **kwargs): 
        if not raw: 
            to_add = Group.objects.get(id=1)  # get_or_create is a better option 
            instance.groups.add(to_add) 
    

这些方法在实现目标方面是否相同?

在 Django 中有更好的“良好实践”吗?

请您参考如下方法:

更新

为了更好地理解 Django 的工作原理,我发现 困惑和解决方案都在 BaseModelForm.save() 中:

    ... 
    if commit: 
        # If committing, save the instance and the m2m data immediately. 
        self.instance.save() 
        self._save_m2m() 
    ... 

在 BaseModelForm._save_m2m() 中:

    ... 
    if f.name in cleaned_data: 
        f.save_form_data(self.instance, cleaned_data[f.name]) 
    ... 

首先保存实例以获取主键(post_save 发出信号),然后保存其所有多对多关系 在 ModelForm.cleaned_data 上。

如果在 post_save 信号期间或在 Model.save() 方法,它将被删除或覆盖 BaseModelForm._save_m2m(),具体取决于 ModelForm.cleaned_data。

transaction.on_commit() - 在此作为解决方案进行讨论 asnwer later and in a few other SO answers from which I was inspired from 并被否决-将延迟信号的变化直到 BaseModelForm._save_m2m() 已结束其操作。

虽然,在某些特殊情况下 transaction.on_commit() is very useful ,在这种情况下是一种矫枉过正,不仅因为它使情况复杂化 一种尴尬的方式(最合适的信号是 m2m_changed as explained here )但是因为完全避免信号,而是 good .

因此,我会尽量给出一个兼顾这两种情况的解决方案:

  1. 如果实例是从Django Admin (ModelForm)中保存的
  2. 如果不使用 ModelForm 保存实例

模型.py

from django.contrib.auth.models import AbstractUser, Group 
 
 
class Person(AbstractUser): 
   def save(self, *args, **kwargs): 
        super().save(*args, **kwargs) 
        if not getattr(self, 'from_modelform', False):  # This flag is created in ModelForm 
            <add - remove groups logic> 

表单.py

from django import forms 
from django.contrib.auth.forms import UserChangeForm 
from django.contrib.auth.models import Group 
from my_app.models import Person 
 
 
class PersonChangeForm(UserChangeForm): 
    def clean(self): 
        cleaned_data = super().clean() 
        if self.errors: 
            return 
        group = cleaned_data['groups'] 
        to_add = Group.objects.filter(id=1) 
        to_remove = Group.objects.filter(id=2) 
        cleaned_data['groups'] = group.union(to_add).difference(to_remove) 
        self.instance.from_modelform = True 
        return cleaned_data 
 
    class Meta: 
        model = Person 
        fields = '__all__' 

这将适用于:

>>> p = Person() 
>>> p.username = 'username' 
>>> p.password = 'password' 
>>> p.save() 

或与:

from django.contrib.auth.forms import UserCreationForm 
from django.contrib.auth import get_user_model 
from django.forms.models import modelform_factory 
 
user_creationform_data = { 
    'username': 'george', 
    'password1': '123!trettb', 
    'password2': '123!trettb', 
    'email': 'email@yo.gr', 
} 
 
user_model_form = modelform_factory( 
    get_user_model(), 
    form=UserCreationForm, 
) 
user_creation_form = user_model_form(data=user_creationform_data) 
new_user = user_creation_form.save() 

旧答案

基于 this 或 that SO 问题以及 标题为“How to add ManytoMany model inside a post_save signal ”的文章我求助于解决方案,是使用 on_commit(func, using=None) :

The function you pass in will be called immediately after a hypothetical database write made where on_commit() is called would be successfully committed.

from django.conf import settings 
from django.contrib.auth.models import Group 
from django.db import transaction 
from django.db.models.signals import post_save 
from django.dispatch import receiver 
 
 
def on_transaction_commit(func): 
    ''' Create the decorator ''' 
    def inner(*args, **kwargs): 
        transaction.on_commit(lambda: func(*args, **kwargs)) 
 
    return inner 
 
 
@receiver( 
    post_save, 
    sender=settings.AUTH_USER_MODEL, 
) 
@on_transaction_commit 
def group_delegation(instance, raw, **kwargs): 
        to_add = Group.objects.get(id=1) 
        instance.groups.add(to_add) 

上面的代码没有考虑到 every login causes a post_save signal 。

深入挖掘

相关 Django ticket 中的一个关键点是 如果 save() 调用是在 原子事务以及依赖于 group_delegation() 函数的结果。

@transaction.atomic 
def accept_group_invite(request, group_id): 
    validate_and_add_to_group(request.user, group_id) 
    # The below line would always fail in your case because the 

提交时 # 在退出这个函数之前不会调用接收器。 if request.user.has_perm('group_permission'): 做一点事() ...

Django docs 更详细地描述了约束条件 on_commit() 成功运行。

测试

During testing ,使用 TransactionTestCase 或 使用 pytest 进行测试时的 @pytest.mark.django_db(transaction=True) 装饰器。

This 是我如何测试此信号的示例。