保存用户后,默认情况下,我需要确保其实例与组关联。
我发现了两种实现方法:
覆盖模型的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”是否有更好的选择?
答案 0 :(得分:-1)
更新
通过更好地了解Django的工作原理,我发现
混乱和解决方案还在于BaseModelForm.save()
:
...
if commit:
# If committing, save the instance and the m2m data immediately.
self.instance.save()
self._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。
因此,我将尝试提供一种适合两种情况的解决方案:
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()
旧答案
基于this或that 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是我测试此信号的一个示例。