[TIL] Django Admin Permissions, etc.

[TIL] Django Admin Permissions, etc.

So far in my main project (more on that to come), the only users of the admin have been myself and my cofounder, so I've made us both superusers. Recently, I had a request to allow an instructor to view and update data from their own courses. This would require me to give them admin access, but in a highly limited view. The good news is that there is a lot of information on how to limit admin access in Django! The bad news is that a lot of it applies to entire models or other use cases that were much less specific than mine. Three major things I learned about:

First, I'm not sure if this is the best or only way to do this, but I created a group (I could not get this to work as permissions for only one user) and limited the permissions to just a few models (and only the required permissions). I'd be interested if there is a way to do this per individual, but I think I might need the group settings sooner rather later anyway.

Second, for those models they are able to use, I needed to limit what they could see to only their students. I was able to do that by adding to the relevant models in admin.py:

    def get_queryset(self, request):
        qs = super().get_queryset(request)

        if request.user.is_superuser:
            return qs
        else:
            return qs.filter(institution=their_institution)

This returns all data for superusers, but only returns whatever you filter on to other users. In my case, I only have two levels of admin user, so this simple filter works well. You'll have to fill in the filter depending on your model.

Third, and this was most difficult to track down, I needed to make sure they couldn't see data outside of their scope when adding new students to their course. For the users model, I was able to add a filter to the user admin like above. But for the Course model, I would still see all courses even when using get_queryset for the CourseAdmin as above. I'm not sure why this is. To fix this, I had to use:

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if not request.user.is_superuser and db_field.name == "course":
            kwargs["queryset"] = Course.objects.filter(institution=institution)
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

This overwrites the data returned for a foreign key in a dropdown on the 'Add' page, and was exactly what I needed!