Conditional content rendering, based on permissions

Hello,

I would like to hear your opinion on how to allow different users to have access to different sections/pages of the site.

Of course, Django offers the built-in permissions system, which I am using extensively. I am facing the following challenge:

Some pages are not directly associated with a model, so it does not make much sense to use any of the provided permissions (e.g. add/change/delete). Neither a custom permission, since again, it needs to be attached to a model. In an example application, both students and teachers have the permission view_grade, but the page will have some different content depending on the user type but only the teacher should access the View Grading page. Another example, is that on the menu at specific (top/bottom) positions, I would like to display different links depending on the user type.

So I am trying to figure out the best approach to this. Firstly, we need to differentiate between the user types, and a common approach to this are the Profiles. I have reached to the conclusion, that I could utilize the Group model that I already use, which can act as the profile. There is not need to create any extra models since no extra data is needed to be stored for each user type.

So far so good. So I was thinking that I can check if the user belongs to a group, and act accordingly in the template. So either using a template tag or context processor, the check can be performed and the appropriate content can be rendered. However, both the above seem wrong, since they seem inefficient and it would be best to perform the check in the view.

What do you think? Is there a better approach? I am not sure if I am missing something obvious here. To summarize, I would like to have a way to check if the user belongs to a specific group, and act accordingly in the template.

Thank you for reading.

I think you might be making some assumptions and drawing conclusions that aren’t entirely accurate.

Why do you think that any given permission “needs to be attached to a model”?

A permission is, at its core, just a label. When you’re testing for a user having a permission, it’s a simple yes/no test - either the user has that permission (directly or indirectly) or they don’t. There’s nothing intrinsic within that test associated with a model. (That the admin app uses that relationship is a characteristic of that application, not of the permission system itself. There is no requirement that you implement that behavior.)

The flip side of that is that you are certainly able to create custom permissions that are associated with models. (In fact, it is required as one of the fields of the Permission model is the content_type of a model.)

More accurately, the better way to think about this is to consider the Group model to define your “roles” within the system.

While you can implement tests for group-membership, it quickly becomes an administrative nightmare as a system becomes more complex. It’s much better to start out with a “Role/Permissions”-based security model.

The most common problem with group-based security is that you can end up with a number of special cases where particular people need custom access, leading to an increasing number of unrelated groups, all needing custom tests. By making the permissions themselves extremely granular, you can create groups for the common cases and either special groups for the uncommon cases or assign permissions to individual users (generally not recommended, but possible).

Hi Ken and thank you for taking the time to respond.

Because the Custom Permissions section of the documentation states that custom permissions are defined in the Meta section of a model. Not sure if there is another section in the documentation for defining customer permissions, but this is the only one I can find. Further, the Permission model’s field content_type requires that when a permission object is created, to be linked to a content type.

I agree, and this is how I want to use it. However, due to the above reasons, not sure how I can use it this way in Django.

Maybe I am missing something here, but how can you create a Permission object, without defining the content_type field?

This is very useful insight. I can see that it will become hard to maintain. I would love to use Permissions to do what I need. To make sure I am following, given the example in my first post, I would need to create view_grading permission, and test on that, right? This is what I want to do, but given my above concerns, it is not clear to me how and where to define permissions which are not associated with any content type.

See Using the Django authentication system | Django documentation | Django

You need to mentally separate the process of creating a Permission from using that permission.

You can create a permission associated with any model if you so desire. (e.g. User) That assignment has absolutely no relationship to how you use that permission in your views.

So yes, you are correct - a Permission must be created with a reference to a content type. However, outside the admin app, it doesn’t much matter which content type is used for that definition.
(Now, having said that, there is another implication of the model being assigned - if you delete the model and clean up the ContentType table, then all Permissions defined associated with that ContentType will also be deleted. If you associate your custom permissions with User or Group, that’s not likely going to be a problem…)

Even though the content type has no implications outside of Django admin, it seems that we need to use Permissions not in the way they were intended by Django. As you have already mentioned, d Defining an irrelevant content type, apart from just being a nuisance, it can be error-prone and potentially confuse the project’s developers.

I guess I would have to apply this given that there is not a more clean solution.

That is not correct. This is precisely the way in which the Permission system is intended to be used. It’s amazingly flexible and robust.

Who said anything about using an “irrelevant” content type? I just said it the usage of the permissions don’t depend upon which model is selected as the base. Pick any appropriate model of your choice.

This is a very clean solution. It works extremely well. (You can search for other posts from me on this topic here where I describe some of the ways we rely upon it.)

I’m getting the impression you’re letting yourself get hung up on something that is little more than a name, and trying to find problems that don’t exist in practice.

If a permission does not logically associate with any model, yet the Permission object requires you to associate the permission with a content type then there is no choice for the developer but to choose an irrelevant content type. It seems that the intended use, is to always associate a permission to a content type, otherwise the content_type foreign key would be optional.

I am sorry, this is how I interpreted your post. I will edit my previous post to reflect that you did not stated that.

I appreciate your perspective, but I disagree. It’s not about creating non-existent problems. There are practical challenges involved in having a permission linked to a content type where there is no logical association, just to be able to create the object.

First, I disagree with the premise that a “permission does not logically associate with any model”. Even if a view does not use any models in the production of a response (e.g. retrieving data from an external API), the permission is logically associated with the User trying to access that view. (or possibly a Group)

For example, consider the following, based upon the example in the docs:

Permission.objects.create(
  codename="can_access_api_view", 
  name="Can access external API view", 
  content_type=ContentType.objects.get_for_model(User)
)

This means that the test for a User having the authority to access a particular view may test something like:
request.user.has_perm("auth.can_access_api_view")
which seems to read quite well to me.

Second, “arbitrary != irrelevant”. And again, you’re confusing the definition of a permission with its usage. They are two different operations occuring at two different points in time, with two separate sets of concerns.

Note that there’s nothing in the above test that in any way indicates that the ContentType being used for the definition of that permission. The people writing the code to test for that permission don’t need that information.

Please identify these “challenges”, and I believe a discussion will reveal that they are not anywhere near as much of a problem as you think they might be. I’ve been building and working on systems using this for almost 10 years now, and you are the first person I have ever encountered to raise a concern in this area.

I have been using Permissions for a while too, and I don’t think I confuse anything.

My past online searches, showed that people have been asking similar questions for quite a while, resorting to workarounds. I believe it is a valid question.

Having said that, your suggestion to associate permissions to the User model does indeed make sense to me (actually you mentioned it previously, but somehow I missed it). It applies perfectly to what I wanted to do, and it did not crossed my mind until you mentioned it! Thanks.

@KenWhitesell @dstav
Great thread very insightful,
I got that there is no logical need for a permission to be related to any Model but django.contrib.auth.models.Permission require to be passed a ContentType to it and content type is for tracking and working with models. so I used below code, create a content type but with blank Model and it seems to work.
is it a good practice? in software engineering process, readability or designe patterns?
will it raise future errors?

In [58]: c1 = ContentType(app_label='', model='')

In [59]: c1.save()

In [60]: Permission.objects.create(
    ...:     codename='test_permission',
    ...:     name='Test Permission',
    ...:     content_type=c1
    ...: )
Out[60]: <Permission:  | Test Permission>

# assigned the permission to u in admin panel

In [61]: u = User.objects.get(username='amir')

In [62]: u.user_permissions.all()
Out[62]: <QuerySet [<Permission:  | Test Permission>]>

No it is not. Any tool that cleans up the ContentType class is going to delete that entry. There may even be other tools that would throw an error if it encounters a ContentType like that.

I suggest you do as I suggested in the other post. Assign these extra non-model-related permissions to the User model. That’s the most straight-forward way to do it.

1 Like

Thanks very much @KenWhitesell for your perspective on the permission setup. This has helped to relieve myself of the contraints I placed upon myself when it came to thinking about using the Django permissions and the role based approach.

One thing we have struggled with is that although one doesn’t need to make use of the default permissions created by Django, they can make it tricky for users to manage roles via the front end. As an example, in one of our applications we have 1,000 permissions. Many of those permissions are irrelevent in our case, and it would be more useful (and easier to understand) if they only saw permissions that are relevent to the app.

Django does have the ability to set default_permissions on the model, but this needs to be done on each model. Do you think there’s any argument for being able to globally set the default permissions in settings, rather than them being hardcoded into the base model here? Can you think of any reason why it hasn’t been implemented this way or why this might be rejected?

I notice that this is an approach that DRF went with. This would allow us to then only add back any permissions we’d want users to have access to. This ticket #29386 demonstrates the issue with doing this on an abstract model class and could have benefited from setting the defauls globally in the app.

Welcome @PrettejohnB !

That’s something for your “permissions management” view to handle.

These default permissions are fundamentally defined for use by the admin. Yes, it’s easy to reuse them, and semantically it makes sense to do so. But altering them is going to affect the ability to use the admin on those models.

Additionally, it’s quite possible - possibly expected - that there are third-party products relying upon those default permission names as well.

So yes, I think such a request would likely be rejected due to the potential down-stream effects on those types of packages.

Thanks @KenWhitesell, that makes sense. I appreciate the time and response. Then I guess all that’s left for me is to just to find some elegent way to filter only those that might be relevent to then end user in the “permissions management” view.