Django Groups not updating when saving User - python

I'm trying to add users that belong to staff to the staff group on change. I've tried it with signals and by overloading save in the User model but neither of them seem to work. Does django has any limitations I'm not aware of when saving?
Here are both code snippets I tried to work with. User is inherited from the AbstractUser and used with the new Django 1.5 feature AUTH_USER_MODEL = 'app.User', Groups are standard auth groups.
class User(AbstractUser):
def save(self, force_insert=False, force_update=False, **kwargs):
if self.is_staff:
if not self.groups.filter(name='staff').exists():
g = Group.objects.get(name='staff')
g.user_set.add(self)
else:
if self.groups.filter(name='staff').exists():
g = Group.objects.get(name='staff')
g.user_set.remove(self)
super(User, self).save(force_insert, force_update)
#receiver(post_save, sender=User)
def my_handler(sender, **kwargs):
user = kwargs['instance']
if user.is_staff:
if not user.groups.filter(name='staff').exists():
g = Group.objects.get(name='staff')
g.user_set.add(user)
else:
if user.groups.filter(name='staff').exists():
g = Group.objects.get(name='staff')
g.user_set.remove(user)

If you use a form(ModelForm) then the reason for disappearing group might be a form.save_m2m() that is called after your user.save(). For example, if you have an empty "groups" field in your form, save_m2m just removes all groups from you user.

Related

How to update an m2m field programmatically in django signals

I'm trying to automatically update an m2m field within my model class. It's an observe-execute if true type, meaning if my model instance has a certain attribute, assign it to this group or that group.
The model class looks like this:
class User(AbstractBaseUser, PermissionsMixin):
is_worker = models.BooleanField(_('Worker'), default=False, help_text='register as a worker')
is_client = models.BooleanField(_('Client'), default=False, help_text='register as a client')
The signal looks like this:
#receiver(post_save, sender=User)
def assign_user_to_group(sender, instance, **kwargs):
if instance.is_client:
instance.groups.add([g.pk for g in Group.objects.filter(name='clients')][0])
elif instance.is_worker:
instance.groups.add([g.pk for g in Group.objects.filter(name='workers')][0])
Usually, I may need to call the save() method on my instance, but the docs states otherwise, plus defying that just gets me a
RecursionError.
Could you please suggest a cleaner approach that best solves this? Thank you.
EDIT
I ended up extending the model's save() method like this:
from django.db import transaction
def save(self, force_insert=False, force_update=False, *args, **kwargs):
instance = super(User, self).save(force_insert, force_update, *args, **kwargs)
transaction.on_commit(self.update_user_group)
return instance
def update_user_group(self):
if self.is_worker:
self.groups.set([g.pk for g in Group.objects.filter(name='workers')])
elif self.is_client:
self.groups.set([g.pk for g in Group.objects.filter(name='clients')])
What is going wrong with your current approach? Using .add() on a many-to-many relation should be sufficient to update the relation without then calling .save()
You're also doing an unnecessary list-comprehension. It is sufficient to do Group.objects.filter(name='workers')[0] or Group.objects.filter(name='workers').first()

Django: OneToOne dropdown in the admin interface and unique associations

Referring to Django - one-to-one modelAdmin i am still searching for a solution to my problem with the admin interface of Django and my OneToOne relationship.
I have the following model which extends the standard User model with an additional attribute is_thing_staff:
class ThingStaff(models.Model):
""" Extends the django user model by a separate model relationship which holds additional user
attributes
"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
# by default a new user is not a staff member which take care of the thing administration
is_thing_staff = models.BooleanField(default=False)
def __str__(self):
return u"{}".format(self.user.username)
class Meta:
verbose_name = "Thing Staff"
verbose_name_plural = "Thing Staff"
If i create a new ThingStaff object in the django admin interface, i can select all users, even if there is already a relationship for a user. Saving a new object with a duplicate association to a user results in an error, that there is already an ThingStaff object associated with that User. So far this is more or less ok.
But why show up possible selections if they would result in an error in the next step? So i excluded them via
from django import forms
from django.contrib import admin
from .models import ThingStaff
class ThingStaffForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ThingStaffForm, self).__init__(*args, **kwargs)
self.fields['user'].queryset = User.objects.exclude(
id__in=ThingStaff.objects.values_list('user_id', flat=True)
)
#admin.register(ThingStaff)
class ThingStaffAdmin(admin.ModelAdmin):
form = ThingStaffForm
Great so far: The already associated users will not show up in the dropdown during the creation of a new ThingStaff object.
But if i want to change an existing association, the related user will also not show up in the dropdown which makes it impossible to reset the is_thing_staff flag.
So my question is: How can i enable this specific user again for the change view in the django admin interface?
Django's ModelForm distinguishes between add and change views (each one has it's on own method). This means that you can override it:
class ThingStaffAdmin(ModelAdmin):
def add_view(self, *args, **kwargs):
self.form = ThingStaffAddForm
return super().add_view(*args, **kwargs)
def change_view(self, *args, **kwargs):
self.form = ThingStaffChangeForm
return super().change_view(*args, **kwargs)
More in the docs:
https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.change_view
your exclution list must be updated and selected user for this ThingStaff must not excluded
update your form like this
class ThingStaffForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ThingStaffForm, self).__init__(*args, **kwargs)
exclude_user = ThingStaff.objects.all()
if self.instance:
exclude_user = exclude_user.exclude(pk=self.instance.pk)
self.fields['user'].queryset = User.objects.exclude(id__in=exclude_user.values('user_id'))
this code check if current form is edit form and have an instance exclude that from exclude list.

How to use Django Signals to run a function after user registration completed?

I'm using django_registration_redux package to handle registering new users. Then I have a set of categories, which I want each new user to follow them by default. So I have to run following code immediately after a user object created:
for category in categories:
f = Follow(user=user.username, category=category)
f.save()
After reading django Docs, I guessed that adding following method to the UserProfile model would work:
def follow_def_cat(sender, instance, created, **kwargs):
if created:
for category in categories:
f = Follow(user=instance.username, category=category)
f.save()
post_save.connect(follow_def_cat, sender=User)
But it seems I couldn't connect saving user signal to the function.
Put your connect instruction out of the signal method.
def follow_def_cat(sender, instance, created, **kwargs):
if created:
for category in categories:
f = Follow(user=instance.username, category=category)
f.save()
post_save.connetc(follow_def_cat, sender=User)
And remember that follow_def_cat is not a model method, you should create it at the same level that the model class:
class UserProfile(models.Model):
...
def follow_def_cat(sender, ...):
...
post_save.connect(follow_def_cat, sender=User)

Adding user to group on creation in Django

I'm looking to add a User to a group only if a field of this User is specified as 'True' once the User is created. Every User that is created would have a 'UserProfile' associated with it. Would this be the correct way to implement such a thing?
models.py:
def add_group(sender, instance, created, **kwargs):
if created:
sender = UserProfile
if sender.is_in_group:
from django.contrib.auth.models import Group
g = Group.objects.get(name='Some Group')
g.user_set.add(sender)
post_save.connect(add_group, sender=UserProfile)
Thanks in advance!
Another option is using a post_save signal
from django.db.models.signals import post_save
from django.contrib.auth.models import User, Group
def add_user_to_public_group(sender, instance, created, **kwargs):
"""Post-create user signal that adds the user to everyone group."""
try:
if created:
instance.groups.add(Group.objects.get(pk=settings.PUBLIC_GROUP_ID))
except Group.DoesNotExist:
pass
post_save.connect(add_user_to_public_group, sender=User)
Only trouble you will have is if you use a fixture ... (hence the DoesNotExists .. )
try this:
def save(self, *args, **kwargs):
# `save` method of your `User` model
# if user hasnt ID - is creationg operation
created = self.id is None
super(YourModel, self).save(*args, **kwargs)
# after save user has ID
# add user to group only after creating
if created:
# adding to group here

How to pass initial parameter to django's ModelForm instance?

The particular case I have is like this:
I have a Transaction model, with fields: from, to (both are ForeignKeys to auth.User model) and amount. In my form, I'd like to present the user 2 fields to fill in: amount and from (to will be automaticly set to current user in a view function).
Default widget to present a ForeignKey is a select-box. But what I want to get there, is limit the choices to the user.peers queryset members only (so people can only register transactions with their peers and don't get flooded with all system users).
I tried to change the ModelForm to something like this:
class AddTransaction(forms.ModelForm):
from = ModelChoiceField(user.peers)
amount = forms.CharField(label = 'How much?')
class Meta:
model = models.Transaction
But it seems I have to pass the queryset of choices for ModelChoiceField right here - where I don't have an access to the web request.user object.
How can I limit the choices in a form to the user-dependent ones?
Use the following method (hopefully it's clear enough):
class BackupForm(ModelForm):
"""Form for adding and editing backups."""
def __init__(self, *args, **kwargs):
systemid = kwargs.pop('systemid')
super(BackupForm, self).__init__(*args, **kwargs)
self.fields['units'] = forms.ModelMultipleChoiceField(
required=False,
queryset=Unit.objects.filter(system__id=systemid),
widget=forms.SelectMultiple(attrs={'title': _("Add unit")}))
class Meta:
model = Backup
exclude = ('system',)
Create forms like this:
form_backup = BackupForm(request.POST,
instance=Backup,
systemid=system.id)
form_backup = BackupForm(initial=form_backup_defaults,
systemid=system.id)
Hope that helps! Let me know if you need me to explain more in depth.
I ran into this problem as well, and this was my solution:
class ChangeEmailForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
self.user = user
super(ChangeEmailForm, self).__init__(*args, **kwargs)
self.fields['email'].initial = user.email
class Meta:
model = User
fields = ('email',)
def save(self, commit=True):
self.user.email = self.cleaned_data['email']
if commit:
self.user.save()
return self.user
Pass the user into the __init__ of the form, and then call super(…). Then set self.fields['from'].queryset to user.peers

Categories

Resources