Django:以编程方式在用户保存时添加组 [英] Django: Programmatically add Groups on User save
问题描述
保存用户后,默认情况下,我需要确保其实例与组关联。
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:
- 如果实例是从Django Admin(ModelForm)保存的,则
- 如果实例已保存而不使用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屋!