1

Exactly like the title says. The post_save receiver is being triggered, and permissions are being changed in these methods, but they remain unchanged on the admin page.

Here's the model I'm using, stripped to just the part in question.

from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from guardian.shortcuts import assign_perm, remove_perm

class Role(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    is_admin = models.BooleanField(default=False)

@receiver(post_save, sender=User)
def create_user_role(sender, instance, created, **kwargs):
    if created:
        Role.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_role(sender, instance, **kwargs):
    if instance.role.is_admin:
        assign_perm('auth.add_user', instance)
    else:
        remove_perm('auth.add_user', instance)
    instance.role.save()

The if..else in the save_user_role method works in the django shell, but not in my signal. I definitely can't save instance again, because that'll cause an infinite loop of post saves.

Saving to my Role model didn't work, as permissions have to be saved to Users and Groups.

I'm guessing this is a misunderstanding of how I'm supposed to use signals, or saving models. Why are my permissions not being saved? What can I do in future to avoid this?

Mitchell
  • 11
  • 4
  • You need to save the instance after calling `assign_perm()`. How does it not work as a signal? What exactly happens? Are you sure those two receivers are handled in the order you expect? Why not do both actions in one receiver? – kichik Nov 17 '17 at 20:48
  • @kichik if I save the instance in the receiver, I triggers the `post_save` signal again, and results in a MaxRecusionDepth error (just tried it again to be safe). If I need to save it, why does it work in the django shell without explicitly saving it? As for receiver order/quantity, the order is triggered correctly, and I have multiple as a proof-of-concept. – Mitchell Nov 17 '17 at 21:24
  • Sorry, that was a typo. I meant to say you *don't* need to save the instance. `assign_perm()` does everything you need. Regardless, can you add more information? – kichik Nov 17 '17 at 21:26
  • Sure, in regards to what? I'll add it to the main body. – Mitchell Nov 17 '17 at 21:29
  • Like what you expect to happen, what actually happens, how it's different in the shell, and how you execute it in the shell. Reading the question as it is right now, I can't figure out how the `if..else` fails. Does it always go to the else? Does it not execute at all? Is `role` not defined? Is `is_admin` wrong? – kichik Nov 17 '17 at 21:46
  • The signal fires the method and it does add/remove the permission *inside the method*, but it doesn't save the permission. If I run `has_perm` on the User in the shell after the signal fires, it will show that it's unchanged (the admin page reflects this too). If I run the code in the shell, it works as expected. Both parts of the `if..else` trigger as expected. Everything is defined properly AFAIK, because it does work in the shell. – Mitchell Nov 18 '17 at 14:51

1 Answers1

0

Solved it.

class Role(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    is_admin = models.BooleanField(default=False)

@receiver(post_save, sender=Role)
def assign_user_perms(sender, instance, **kwargs):
    if instance.is_admin:
        assign_perm('auth.add_user', instance.user)
    else:
        remove_perm('auth.add_user', instance.user)

@receiver(post_save, sender=User)
def create_user_role(sender, instance, created, **kwargs):
    if created:
        Role.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_role(sender, instance, **kwargs):
    instance.role.save()

Since is_admin is a member of Role, it makes more sense for the signal to trigger on the save of Role. It works here as expected.

Mitchell
  • 11
  • 4