View access based on group membership

Hello,

I wanted to restrict access to views based on the users membership to group. I have used the @user_passes_test decorator testing to see if a user belongs to a group. However, to do this I have to create a function that filters for each group to later use in the decorator. This is because when the test function is within the decorator I cant pass a list with the groups that should get access. Is there another approach to this? Also how would this work in class based views?

Example of function used in decorator:

def op1_user(user):
    if user.groups is None:
        return False
    return user.groups.filter(name="OP1").exists()

Thanks,

Hey there!
Can you explain a bit more about your need?
Do you need to filter for more than one group?
Do you need to filter for a specific group name, but don’t want to create a function to each one of it?

Yes I would like to filter for one or more groups and not create a function for each case.

Right, so one approach is a “decorator factory”.
A decorator factory is a function that creates the decorator function.
It looks like this.

def is_on_group_check(*groups):
  def on_group_check(user):
     if user.groups is None:
       return False
     return user.groups.filter(name__in=groups).exists()

  return on_group_check

Then, you can create a decorator for each group of groups you need, like:

on_op1_group = is_on_group_check("OP1")
on_op1_to_3_groups = is_on_group_check("OP1", "OP2", "OP3")

Then you will use the created decorators on the user_passes_test decorator.

@user_passes_test(on_op1_group)
def your_view(request):
   ...

How could it be done for class based views?

Actually, what I would say here is that this is entirely the wrong approach.

I would suggest that the “best Django process” is to use the built-in permission system rather than group level checks. It’s a lot more flexible and easier to administer.

Briefly, you define or identify a permission that is needed for each view, then assign that permission to all groups that need it. Then you use one of the permission tests to check to see if that user is allowed to access that view.

The concept is referred to as “Role-based security” and is a common pattern beyond Django. If you search through the forum, you can find other posts where this has been discussed in more detail.

1 Like

Are permissions created per model not per view?

Permissions are created however you want them to be.

The system (by default) creates a set of permissions for each model created by Migrations.

How you create permissions is up to you.

How can they be created by view? Custom permissions are created around models as well …

My issue is I never see how permissions can be custom made to relate with views the documentation always seems to relate these more closely to models.

You’ve come to a “reasonable, but inaccurate” conclusion from the documentation.

Please read the full thread at Conditional content rendering, based on permissions

Yes I think I have the same issues as those reflected in the thread. After reading this I have the following question, how can I attach the permissions to the users that are show in the django admin?(Where is the class definition in which under the meta tag I can add the necessary permissions)

I’m sorry, I don’t understand what you’re trying to ask here.

If the permissions have to be added to some class that should be the user class used in the django admin(modify the meta of such class to contain the permissions). Is that the correct approach? As shown here Managing User Permissions in Django - Honeybadger Developer Blog.

The permissions do not need to be added to a class.

An instance of the Permission model can be created with a reference to a class.

See Programmatically creating permissions

1 Like

Do your views not present Models to your users? Would maybe the permission fit to the most prominent on a view?

If not, you could create a “permission” app with one empty model that just holds permissions. This is something I have done in the past, although I would probably not do this again. But it works and you don’t have to think about how you programmatically add permissions that should be in your project by default.

Hello,

Thanks I decided to follow this path since my views don’t correspond one to one with any models. My main objective is to restrict access to views more than restrict access to models. In this app I have created one permission to access each view. In that app I also create the permission groups.

My question is if the model has to be a empty model or if it should be an AbstractUser model? This is because in Customizing authentication in Django | Django documentation | Django under Substituting a custom User model it states this can be done to keep functionality but add permissions. I believe this could be my case.

Thanks,

Neither. The recommendation I made in the thread referenced above was to use the User model you are using in your project. (Either the system-default user or a custom user, whichever is the case.)

For possible clarification: A custom user model defined as
class CustomUser(AbstractUser):
is not an “AbstractIUser model”. It’s a model that inherits from AbstractUser.

Yes i am referring to a model that inherits from AbstractUser.

Thanks,

So yes, that would be the model I would use as the “base” for any permissions that I can’t otherwise assign to a more appropriate model.

Thanks I decided to follow this path since my views don’t correspond one to one with any models. […]
My question is if the model has to be a empty model or if it should be an AbstractUser model? […]

I may just not understand the question right, … Are you referring to the path I described? If not, disregard what comes next. Otherwise, you don’t need a user model.

Here’s how it looks on my side. I have an app “permissions” with just a models.py (and the app.py).

permissions\models.py looks like this. What you see is the entire file.

from django.db import models


class Permissions(models.Model):
    class Meta:
        default_permissions = ()
        permissions = (
            ("permission_name_1", "Human-readable permission description"),
            ("permission_name_2", "Human-readable permission description"),
            ...  # Add permissions as needed
        )

And then in the views you would just use the permission_required decorator.

...

@login_required
@permission_required(["permissions.permission_name_1"])
def some_view(request):
    ...

...

Then in the admin UI you can assign users the permissions as needed.

Again, the above makes sense because I have certain permissions that don’t apply to a certain model or a specific app in my project. It may be up for debate if that’s a good thing, but for the use-case I have, this is a rather slim and simple solution.

1 Like