Package: rest-framework-roles
REST Framework RolesRole-based permissions for Django REST Framework.Least privileges by default.Human readabledeclarativeview-based permissions.Switch between DRF's permission_classes and this easily.Protects you from accidentally exposing an endpoint onview redirections.Generic & flexible. You decide the where and how of your access logic and storage.This works as a replacement forpermission_classeson individual classes. This makes it easy to switch between this and the normal DRF behaviour depending on your needs.Note that alsoDEFAULT_PERMISSIONS_CLASSESis patched so by default all endpoints will be denied access by simply installing this.InstallationInstallpip install rest-framework-rolesEdit yoursettings.pyfileINSTALLED_APPS={..'rest_framework','rest_framework_roles',# Must be after rest_framework}REST_FRAMEWORK_ROLES={'ROLES':'myproject.roles.ROLES',}Now all your endpoints default to403 Forbiddenunless you specifically useview_permissionsor DRF'spermission_classesin view classes.By default endpoints fromdjango.contribwon't be patched. If you wish to explicitly set what modules are skipped you can edit the SKIP_MODULES setting like below.REST_FRAMEWORK_ROLES={'ROLES':'myproject.roles.ROLES','SKIP_MODULES':['django.*','myproject.myapp55.*',],}Setting permissionsFirst you need to define some roles like belowroles.pyfromrest_framework_roles.rolesimportis_user,is_anon,is_admindefis_buyer(request,view):returnis_user(request,view)andrequest.user.usertype='buyer'defis_seller(request,view):returnis_user(request,view)andrequest.user.usertype='seller'ROLES={# Django out-of-the-box'admin':is_admin,'user':is_user,'anon':is_anon,# Some custom role examples'buyer':is_buyer,'seller':is_seller,}is_admin,is_user, etc. are simple functions that takerequestandviewas parameters, similar toDRF's behaviour.Next we need to define permissions for the views withview_permissions.views.pyfromrest_framework.viewsetsimportModelViewSetfromrest_framework.decoratorsimportactionfromrest_framework_roles.grantingimportis_selfclassUserViewSet(ModelViewSet):serializer_class=UserSerializerqueryset=User.objects.all()# you can define permissions at the view levelview_permissions={'create':{'anon':True},# only anonymous visitors allowed'list':{'admin':True},'retrieve,me':{'user':is_self},'update,update_partial':{'user':is_self,'admin':True},}@action(detail=False,methods=['get'])defme(self,request):self.kwargs['pk']=request.user.pkreturnself.retrieve(request)By default everyone is denied access to everything. So we need to 'whitelist' any views
we want to give permission explicitly.For redirections likeme(which redirects toretrieve), we need to give the same permissions to both or else we'll get 403 Forbidden.In a view you can always check_view_permissionsto see what permissions are in effect.A request keeps track of all permissions checked so far. So  redirections don't affect performance since the same permissions are never checked twice.Advanced setupBypassing the frameworkIf you want to bypass the framework in a specific view class just explicitly set thepermission_classes.classMyViewSet():permission_classes=[AllowAny]By default when you install DRF, every class gets automatically populatedpermission_classes = [AllowAny]which is really a bad idea. If for some reason you wish to get the same behaviour, you'd need to addpermission_classes = [AllowAny]on every individual class.Granting permissionYou can use the helper functionsalloforanyofwhen deciding if a matched role should
be granted accessfromrest_framework_roles.grantingimportallofdefnot_updating_email(request,view):return'email'notinrequest.dataclassUserViewSet(ModelViewSet):view_permissions={'update,partial_update':{'user':allof(is_self,not_updating_email),'admin':True,},}In the above example the user can only update their information only while not trying to update their email.You can put all these functions inside a new filegranting.pyor just keep them close to the views, depending on what makes sense for your case. It'simportant to not mix them with the rolesthough to keeps things clean; (1) a role identifies someone making the request while (2) granting determines if the person fitting tha role should be granted permission for their request.Keep in mind that someone can fit multiple roles. E.g.adminis also a user (unless you change the implementation ofis_userandis_admin).Optimizing role checkingYou can change the order of how roles are checked. This makes sense if you want
less frequent or expensive checks to happen prior to infrequent and slower ones.fromrest_framework_roles.decoratorsimportrole_checker@role_checker(cost=0)defis_freebie_user(request,view):returnrequest.user.is_authenticatedandrequest.user.plan=='freebie'@role_checker(cost=0)defis_payed_user(request,view):returnrequest.user.is_authenticatedandnotrequest.user.plan@role_checker(cost=50)defis_creator(request,view):obj=view.get_object()ifhasattr(obj,'creator'):returnrequest.user==obj.creatorreturnFalseIn this example, roles with cost 0 would be checked first, and lastly thecreatorrole would be checked since it has the highest cost.Note this is similar to Django REST'scheck_permissionsandcheck_object_permissionsbut more generic & adjustable since you can have arbitrary number of costs (instead of 2).