Django:以编程方式在用户保存时添加组

时间:2018-12-03 22:41:57

标签: python django django-models django-users django-related-manager

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

我发现了两种实现方法:

  • 覆盖模型的save()方法

    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)
    
  • 捕获后保存信号:

    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)
    

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

在Django中,“ Good Practice”是否有更好的选择?

1 个答案:

答案 0 :(得分:-1)

更新

通过更好地了解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()-在本文中作为解决方案进行讨论 后来提出了一些其他的答案,从中我得到启发 并被投票-将延迟信号的变化,直到 BaseModelForm._save_m2m()已结束其运作。

这是一个过大的杀伤力,不仅是因为它使情况复杂化。 笨拙的方式,但是因为完全避免了信号,所以 good

因此,我将尝试提供一种适合两种情况的解决方案:

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

models.py

from django.contrib.auth.models import AbstractUser, Group


class Person(AbstractUser):
   def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        try:
            if not self.from_modelform:  # This flag is created in ModelForm
                <add - remove groups logic>
        except AttributeError:
            pass

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

旧答案

基于thisthat SO问题以及一个 我转向的解决方案标题为“ How to add ManytoMany model inside a post_save signal”的文章是使用on_commit(func, using=None)

  

您传入的函数将在   假设在调用on_commit()的情况下进行的数据库写操作是   成功提交。

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中的一个关键点是 如果在内部{@ {1}}中进行了调用,则上述代码将不起作用 原子交易以及依赖于 save()函数的结果。

group_delegation()

on_commit         #退出此功能之前不会调用接收器。         如果request.user.has_perm('group_permission'):             做点什么()         ...

Django docs更详细地描述了限制条件 @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 成功运行。

测试

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

This是我测试此信号的一个示例。