7

Hello, I'm using Django 1.9. Trying to add a user to group on creation or save. Using either user.groups or groups.user_set.add don't work, mix and match with pre- and post-save below.

These answers aren't working for me, and so this isn't a dupe: Adding user to group on creation in Django
Adding a user to a group in django
Add users to groups in Django

I have tried both methods in both pre- and post-save handlers.

@receiver(pre_save, sender=User)
def user_presave_handler(sender, instance, **kwargs):
    if instance.is_staff and not instance.is_superuser:
        # Grant all permissions
        try:
            instance.groups.add(Group.objects.get(name='staff_user'))
        except Group.DoesNotExist:
            pass

@receiver(post_save, sender=User)
def user_postsave_handler(sender, instance, **kwargs):
    if instance.is_staff and not instance.is_superuser:
        try:
            g = Group.objects.get(name='staff_user')
        except Group.DoesNotExist:
            pass
        else:
            g.user_set.add(instance)
            g.save()

You can mix and match which method is used where, I have tried it. I don't use more than one method in testing. After hitting the save button on user admin page, the user is not shown as in the group.

I double-checked that the handlers are getting called, user logic is correct, etc.

Is there something I'm doing wrong, or something that has changed in 1.9 to break the old methods?

Thanks!

Edit: For those asking, the group is created like this:

group, __ = Group.objects.get_or_create(name='staff_user')

permissions = Permission.objects.all()
for p in permissions:
    group.permissions.add(p)
group.save()

I have debugged it and the group definitely exists, though maybe I made it wrong and so it won't be applied?

Community
  • 1
  • 1
std''OrgnlDave
  • 3,912
  • 1
  • 25
  • 34
  • `instance.groups.add`should work... Are you sure the group exists? – TheWaveLad Jan 10 '17 at 16:56
  • I suggest adding a breakpoint in your receiver to figure out what really happens (`import pdb; pdb.set_trace()`) – Daniel Hepper Jan 10 '17 at 16:58
  • It looks correct. Have you tried calling the method manually and seeing if it works like that? – Jann Jan 10 '17 at 17:21
  • @Jann the methods both do a whole lot more to the user object, including verifying lower-case, setting up permissions for an external server, starting some Celery tasks, generating regexes, and a few more things. It would be very difficult to call either directly. However, I will simplify them and try. – std''OrgnlDave Jan 10 '17 at 17:54
  • @TheWaveLad I edited to show how I create/update the group. Perhaps I did it wrong? – std''OrgnlDave Jan 10 '17 at 17:54

3 Answers3

3

I think it's related to this: Issue with ManyToMany Relationships not updating inmediatly after save . Somethere reports in a comment that it's still current in 1.10.

The problem is that you can also select groups on the admin 'add user' page; and then when you save the User, the following happens:

  1. The User is saved
  2. The post-save signal happens, and you add the User to the group
  3. All groups of the User are cleared
  4. The groups that were chosen on the add user page are added to the User.

You can use a m2m_changed signal, apparently. I've never used it, but I think something like this should work:

from django.db.signals import m2m_changed

@receiver(m2m_changed, sender=User.groups.through)
def user_groups_changed_handler(sender, instance, action, **kwargs):
    if action == 'post_add' and instance.is_staff and not instance.is_superuser:
        if not instance.groups.filter(name='staff_user').exists():
            try:
                g = Group.objects.get(name='staff_user')
            except Group.DoesNotExist:
               pass
            else:
                instance.groups.add(g)

This is assuming the instance that the signal receives is a User, otherwise it's going to look slightly different. The check for the action and the check whether the group was already added are to prevent infinite loops. The sender is the hidden 'through' table for the many to many relation, User.groups.through.

Community
  • 1
  • 1
RemcoGerlich
  • 30,470
  • 6
  • 61
  • 79
  • This is brilliant and gets us a step closer. Unfortunately the signal is only sent when M2M is changed. So it works, say, when I add the user to a DIFFERENT group, not just automatically. – std''OrgnlDave Jan 11 '17 at 18:03
  • Investigating, a post_remove is sent regardless of if it is changed, just like you said. So the logic ``if (action == 'post_save' or action == 'post_remove') and instance.is_staff and not instance.is_superuser:`` seems to work currently, I will test it further – std''OrgnlDave Jan 11 '17 at 18:07
  • Might want to add post_remove to the answer for future Googles on the topic, thanks though! Bounty in 23 hours – std''OrgnlDave Jan 12 '17 at 17:15
  • Note to any folks looking to answer: I will be awarding the bounty to this when the time comes – std''OrgnlDave Jan 12 '17 at 17:18
3

Python 3, Django==2.1.7

def add_user_to_group(sender, instance: User, created: bool, **kwargs):
    try:
        if created:
            group = Group.objects.get(name=instance.user_type)
            instance.groups.add(group)
            instance.save()
    except Group.DoesNotExist:
        pass


models.signals.post_save.connect(add_user_to_group, sender=User)
srgi0
  • 3,319
  • 1
  • 23
  • 20
0

In my case I was trying to map the is_staff attribute to a group called "staff". Thanks to @RemcoGerlich's answer I was finally able to come up with something that worked for me:

@receiver(post_save, sender=User)
def save_user_person(sender, instance, **kwargs):
    user = instance
    instance.person.save()
    group = Group.objects.get(name='staff')
    if user.is_staff and not user.groups.filter(name='staff').exists():
        user.groups.add(group)
    elif not user.is_staff and user.groups.filter(name='staff').exists():
        user.groups.remove(group)

@receiver(m2m_changed, sender=User.groups.through)
def sync_staff_to_group(instance, action, **kwargs):
    user = instance
    if action == 'post_remove' or action == 'post_add' or action == 'post_clear':
        group = Group.objects.get(name='staff')
        if user.is_staff and not user.groups.filter(name='staff').exists():
            user.groups.add(group)
        elif not user.is_staff and user.groups.filter(name='staff').exists():
            user.groups.remove(group)
Scott Jungwirth
  • 6,105
  • 3
  • 38
  • 35