Django: Design views that check user permissions the same way it does on the backend admin page.

By Samuel Muiruri | Dec. 28, 2019 | Django


So let's say your get a project and that requires you to have users in departments with limited rights to data from specific models depending on their work, the accounting don't need access to the logistics and vice versa. One way to do that would be just having a user flag also known as a boolean field that determines if you have those rights, but as the complexity continues and there's a section inside accounting that needs only read access and so on you'll need to have more and more flags added to the user model.

Now last I checked you couldn't edit Django's user model you could only extend it and usually the way that happens is having a model with a ForeignKey to it, then this UserProfile can include all the extra fields you need. Thing is then if your user model you created happens to have staff rights which means they can log into the backend and also have additional rights, a tech savvy user would only need to know that django's admin is /admin and try logging in there to find out they've got more rights on the backend than they do on the front end. You might also provide no rights to their user model which would be the safe method but with that only you and any other superuser will be able to work with the backend if ever they need to.

I'm not presuming this is the best method since even in this project there were organizations that were supposed to be separate so they would not be able to see each other's data but from the backend this user with the rights to edit invoices would have rights to edit every organisation's invoice, so this only applies from a single organisation stand point and also the powers that be that employed you don't mind if they have access to the backend because it's just the same thing, same rights, same content. 

If your in that place then this is how you can leverage what you probably have done, assigned each user certain rights from the backend whether using groups or not also on the frontend. 

You have your model, I'll be working with a Company model to keep things simple, the example of the model layout above.

If you want to follow this step by step, make sure the app name is also called company. So now on the backend as usual you have your user model with rights to the company model, I recommend having your initial superuser model then create another with no rights to check on this.

From your user model from the shell or views you can check the permissions using user.get_all_permissions() and from there you'll find these:

company.view_company, company.add_company, company.delete_company, company.edit_company

among others from the other models you likely have in your project, but we'll be working with these. So if a user has these I can tell if they have permission to view, add, delete or edit a model's data.

Now the view below will return the data as a json result, I'm going to check whether a user has rights to view a model's data and if so return the data with pagination included. This is useful since these days with frontend frameworks that just need a source of data as json and likelihood you'll either be working as a frontend or backend developer it's good practice to be working this way since it will allow you to easily transition from a fullstack dev to a backend or frontend dev. Now in my case I'd be using vuejs likely for this on the front end but that part I'll skip for now since it would make the article unnecessarily long. 

Now above you have the view layout, I first check if the user is logged in with request.user.is_authenticated then with the list of permissions with permissions = list(user.get_all_permissions()) all I need to do is check is if the user has permissions to view the companies with if 'company.view_company' in permissions: and if yes get the list of companies, use pagination to get the content for that page with response['companies'] = p.page(page).object_list if you check the function's definition you'll see page=1 so it has a page number which is either provided or it uses 1 as the defaut.

if not items_per_page:

     items_per_page = len(items_list)

And finally as a final feature above if you don't provide the items per page it will use the length of all items so pagination will basically always be a single page, a feature you can use when you don't want to use pagination.

and finally the urls' path like so

path('view-companys-json/<int:items_per_page>/<int:page>', company_views.get_companies_json),

and with that you can control the user permission without too much boiler plate code or model definitions, you keep it simple, neat and works with the existing structure provided by Django.