Groups per object using Django and django-guardian object permissions
Asked Answered
C

1

13

I'm currently creating a structure where I have employees which belong to a company. Within this company I need to be able to create several groups. Ranks if you will. You could assign less permissions to lower ranks and more permissions to higher ranks.

I want to go for object level permissions and I noticed the django-guardian project gave me exactly what I needed. It works with the native User and Group objects so I'm now trying to find a way to implement the native group object in a company object.

Problems I face is that name in group is unique. So if 2 companies add the same group, errors will occur.

I found an implementation that works in a way but seems quite 'hacky' to me. In my company I declared a group variable that references Group:

class Company(models.Model):
    ...
    groups = models.ManyToManyField(Group, through='CompanyRole')

CompanyRole basically houses the group name and a reference to company and group

class CompanyRole(models.Model):
    group = models.ForeignKey(Group)
    company = models.ForeignKey(Company)
    real_name = models.CharField(max_length=60, verbose_name=_('Real name'))

    objects = CompanyGroupManager()

I created a custom manager with a convenient method to add a new 'company group'

class CompanyGroupManager(models.Manager):
    def create_group(self, company, group_name):
        un_group_name = str(company.id) + '#' + group_name
        group = Group.objects.create(name=un_group_name)
        company_group = self.model(
            real_name=group_name, 
            company=company, 
            group=group
        )

        company_group.save(using=self._db)
        return company_group

Here's the part I don't really feel confortable about. In order to change the problem with the unique name on the Group model I used a combination of the company id, a hash sign and the actual group name to avoid clashes.

Now my question is: are there better methods in my scenario, am I missing something or is this a good way of accomplishing what I need?

Choroid answered 4/5, 2014 at 18:36 Comment(3)
Can you use hard-wired custom permissions in your Company model instead of groups?Kornegay
Basically want I want to do is. In a company you should be able to make departments which you can give global permissions and object permissions. Suppose you have a department called junior programmers, I want to be able to give them full permissions on certain projects but globally give them very little permissions to other projects that are created. That's possible now but my solution seems rather hacky. What do you mean by hard wired custom permissions?Choroid
Hi Jeffrey, I mean using the permissions Meta attribute to define your own permissions which you can check against. You have mentioned several other models not present in your posted code, projects and departments, so I'm a bit unclear on your model structure. But, I think I see what you want to accomplish. I'll add an answer with a setup I've used in the past. Can you provide an example list of the permissions/roles you want to have for users?Kornegay
G
3

Unfortunately there is no way of getting around the unique requirement, that is because this field is used as the id: https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.Field.unique

Your options are the following:

1) Mocking the model.

You would basically just create a new Group model that doesn't have the unique requirement. The downside here is that you'd need to use it everywhere, so if this requires updating 3rd party apps, it might not be worth it.

2) make the name you unique. (As you did)

Make sure that you document your convention well, so that all future coders will know what they are looking at.Something like "company name"#"group name" could make more intuitive sense than an id. If the a hash might appear in either then use a more certain delimiter ("__" is a relatively common way of connecting related concepts in django, I might go for this).

I would recommend that you add the following to make it easy for you to access the name.

def get_name(self):
    # Explain how you get the group name from your uniqueified name
    return self.name.split('#')[1] 

Group.add_to_class('get_name', get_name)

When you access your group's name in your app, just do:

my_group.get_name()

You might also want to put the generating the uniqueified name into an overridden version of the save(). This would give you nicer split between model and view...

Gine answered 15/5, 2014 at 9:16 Comment(1)
This is exactly what I ended up doing as well. :) However, the disadvantage I found out about this approach is the following: For example you have an model: MyModel. With permissions: view_mymodel, change_mymodel, add_mymodel, delete_mymodel. For viewing, changing and deleting permisions, you add those to an object and check the permission (user.has_perm('view_mymodel', obj)). But for adding you cannot and will use user.has_perm('add_mymodel'). What if the user for one organisation is in a group with the add permissions and in the other organisation the user is in a group without?Flanch

© 2022 - 2024 — McMap. All rights reserved.