Django:以编程方式在用户保存时添加组 [英] Django: Programmatically add Groups on User save

查看:82
本文介绍了Django:以编程方式在用户保存时添加组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

After a user is saved, I need to make sure that its instance is associated with a group by default.

我发现了两种实现方法:

I have found two ways to achieve that:


  • 覆盖模型的 save()方法

models.py:

models.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)


  • 捕获后保存信号:

  • Capturing a post_save signal:

    signals.py:

    signals.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)
    


  • 这些方法在实现目标上是否相等?

    Are these methods equal in achieving their goal?

    用Django术语中的 Good Practice更好吗?

    Is there a better one in Django terms of "Good Practice"?

    推荐答案

    更新

    对Django的工作方式有了更好的了解,我发现
    的困惑以及解决方案都在 BaseModelForm.save()

    Acquiring a better understanding of how Django works, I see that the confusion and also the solution lie in 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

    The instance is first saved to acquire a primary key (post_save signal emmited) and then all its many to many relations are saved based on ModelForm.cleaned_data.

    如果在 post_save 信号或在
    中使用 Model.save()方法,它将从
    BaseModelForm._save_m2m( ),具体取决于
    ModelForm.cleaned_data 的内容。

    If any m2m relation has been added during the post_save signal or at the Model.save() method, it will be removed or overridden from BaseModelForm._save_m2m(), depending on the content of the ModelForm.cleaned_data.

    transac tion.on_commit()-稍后在此
    问答中作为解决方案进行讨论,在其他一些我得到
    启发而又被否决的SO答案中,它将延迟直到
    BaseModelForm._save_m2m()结束其操作为止的信号变化。

    The transaction.on_commit() -which is discussed as a solution in this asnwer later on and in a few other SO answers from which I was inspired and got downvoted- will delay the changes in the signal until BaseModelForm._save_m2m() has concluded its operations.

    尽管如此,在某些特殊情况下 transaction.on_commit() 非常有用,在这种情况下是一个过大的杀伤力,不仅是因为它使
    的情况复杂而笨拙(最合适的信号是 m2m_changed 如此处所述)但是因为完全避免信号,所以

    Although, in some special cases the transaction.on_commit() is very useful, in this case is an overkill, not only because it is complexing the situation in an awkward manner (the most suitable signal is m2m_changed as explained here) but because avoiding signals altogether, is rather good.

    因此,我将尝试给出一种能够迎合您需求的解决方案两次:

    Therefore, I will try to give a solution that caters for both occasions:


    1. 如果实例是从Django Admin(ModelForm)保存的,则

    2. 如果实例已保存而不使用ModelForm

    models.py

    models.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>
    

    forms.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()
    




    旧答案

    基于 SO问题以及题为 ; 如何在post_save
    信号内添加ManytoMany模型
    "我求助的解决方案是使用 on_commit(func,using = None)

    Based on either this or that SO questions along with an article titled "How to add ManytoMany model inside a post_save signal" the solution I turned to, is to use on_commit(func, using=None):


    您传入的函数将在调用了on_commit()的
    个假设数据库写入之后,将立即成功调用

    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)
    

    上面的代码没有考虑到每个登录会导致
    后保存信号

    The above code does not take into account that every login causes a post_save signal.

    相关 Django票证就是上面代码中的
    将不起作用如果在
    原子事务内进行了 save()调用,并且验证取决于
    结果group_delegation()
    函数。

    A crucial point made in the relevant Django ticket is that the above code will not work if a save() call is made inside an atomic transaction together with a validation that depends on the result of the group_delegation() function.

    @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
    

    on_com mit
    #接收器在退出此功能之前不会被调用。
    if request.user.has_perm('group_permission'):
    do_something()
    ...

    on_commit # receiver wouldn't be called until exiting this function. if request.user.has_perm('group_permission'): do_something() ...

    Django文档更详细地描述了
    on_commit()成功运行。

    Django docs describe in more details the constraints under which on_commit() successfully works.

    在测试期间,使用
    TransactionTestCase
    <$在使用pytest测试时,使用c $ c> @ pytest.mark.django_db(transaction = True)
    装饰器。

    During testing, it is crucial to use the TransactionTestCase or the @pytest.mark.django_db(transaction=True) decorator when testing with pytest.

    是我测试此信号的示例。

    This is an example of how I tested this signal.

    这篇关于Django:以编程方式在用户保存时添加组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆