Same user different group permission

Im building a project to manage our “studies”.
A study contains a bunch of tasks, documents, todos, contacts, leads and so on.

A User can have access to one or multiple of these studies, but should not be able to know of or access studies where access have not been granted.

Within a study, the user is assigned to a group with permissions specific to each study, so that the same User can have different permissions based on which study he has entered.

How should i implement such authorization? I have never worked with django permissions so im in uncharted territory.

Given some basic models like:

class Study(BaseModel):
    title               = models.CharField(max_length=25, unique=True)

class Member(BaseModel):
    user        = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)  
    study       = models.ForeignKey(Study, on_delete=models.CASCADE)

Is it possible, and would it even make sense, to add the django group permissions on the Member model?

Or how would you suggest implementing such functionality?

Guidance or tutorials are very welcome.

Bw Martin

Hello there, maybe this is two separate topics?

I see that this is something that you would need some kind of control on which row is shown to the user, in that case you can use the Member model to that. Like so:

Member.objects.filter(user=request.user)

From this you know which “Study” an user have access to.

But i don’t see how a permission/group would enter into this scenario, as you described here:

What permissions are you talking about?

Hi Leandro, thanks for getting back to me!

Yes i use Member.objects.filter(user=request.user) to let the user see which studies he can enter.

Upon entering one such study, i want to be able to give the user permissions within this specific study. Example:
In study 1: The user is in the study-admin-permission group so he can view edit and delete all documents, leads, processes.
In study 2: The user is in the study-secretary-permission group, so he can view and edit but not delete documents, leads, processes.

Does that make sense?

Looks like, from your definition, that each Study must have a Group associated with.
This would enable that each study controls, what permissions all Members inside that study have.
But if you need more granularity, for example, inside each study, each user can have different permissions, then you would need something else, like having a Group associated to each Member.

You can choose one of them, or have both.

Just to remind you, this won’t right of the box with the permission_required and the user.has_perm. You’ll need to write your own way to checking these permissions based on the Member object. Something like:


class Member(BaseModel):
    user        = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)  
    study       = models.ForeignKey(Study, on_delete=models.CASCADE)

    def has_perm(self, perm):
         has_default_perm = user.has_perm(perm)
         if has_default_perm:
                return True
         # otherwise check if the user has permission based on the study group model
         study_group_perms = {"%s.%s" % (ct, name for ct, name in self.study.group.permission_set.all().order_by().values_list("content_type__app_label", "codename") }
         return perm in study_group_perms

Thank you Leandro, it is indeed a version of the latter i am trying to implement.

I am very new in the domain of django permissions, so i have a hard time figuring our what the study.group in

study_group_perms = {“%s.%s” % (ct, name for ct, name in self.study.group.permission_set.all().order_by().values_list(“content_type__app_label”, “codename”) }

refers to? Should i make a group model or where does it come from?

In addition, when dissecting your answer this past week, i have been wondering if it would be possible to make 4 django Groups, alá Noob, Midi, Grownup, Admin, which different permissions of course. And then in the member model, make some sort of reference field to which of these Groups the member is associated to in this study.

alá:

class Member(BaseModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
study = models.ForeignKey(Study, on_delete=models.CASCADE)
group = models.ForeignKey(Group…)

And then use the group field to check for the perms as per your def has_perm(self, perm):?

My thinking is that this way User1 could have a say 3 member objects:

member_instance_1 = {user: User1, study: study1, group: admin}
member_instance_2 = {user: User1, study: study2, group: noob}
member_instance_3 = {user: User1, study: study3, group: grownup}

Well, an group on the Study model would be an ForeignKey to an Group instance, how would you add this group to the Study is up to you to define. But based on your answer I don’t think that this is the behavior you want.
Using the approach of having a Group inside the Study model, would imply that all of the Members of the Studywould have the same exact set of permissions.

That’s definitely possible.

I believe that this is the approach that you’re looking for from what you described.

Having a Group in each Member makes that a user would have different permissions based on each Study "Membership. This would also allow that different users from the same Study to have different permissions from each other.

Exactly.

It would be up to you to decide how much “granularity” and “control” you want over the permissions of these models.
As I said before, if you want that:

  • Members of the same Study have different permissions based on the Study, but all Members wouldn’t have different permissions than other members, you want to add a Group of permissions to the Study model.
  • Members of the same Study have different permissions than each other Member then you would add a Group of permissions to the Member model.

Cool, thanks Leandro!

I am now running the model:

class StudyAccess(BaseModel): 
     user        = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="user_access", on_delete=models.CASCADE)  
     study       = models.ForeignKey(Study, on_delete=models.CASCADE)
     group       = models.ForeignKey(Group, related_name='access_groups', null=True, on_delete=models.RESTRICT)
     
    def has_perm(self, perm):
        has_default_perm = self.user.has_perm(perm)
        if has_default_perm:
            return True

        if self.group:
            perms = set(self.group.permissions.values_list('content_type__app_label', 'codename'))
            perm_parts = perm.split('.')
            if len(perm_parts) == 2 and (perm_parts[0], perm_parts[1]) in perms:
                return True
        return False

And ive set up some groups to choose from.

I apologize if im asking too much, but in the templates, do you have a suggestion as to how i use the has_perm()? Id like to avoid manually adding the study_access to each view.
Would it make sense to add a context processer to add the study_access to all views? Or is there a smarter way?

Bw Martin