Search code examples
djangodjango-modelsdjango-signalsdjango-permissionsdjango-guardian

Django Signal Triggered But Not Saving


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?


Solution

  • 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.