Package: django-cancan
django-cancandjango-cancanis an authorization library for Django. It works on top of default Django permissions and allows to restrict the resources (models and objects) a given user can access.This library is inspired bycancancanfor Ruby on Rails.Key featuresAll of your permissions logic is kept in one place. User permissions are defined in a single function and not scattered across views,
querysets, etc.Same permissions logic is used to check permissions on a single model instance and to generate queryset containing all instances that the user can accessEasy unit testingIntegration with built-in Django default permissions system and Django admin (coming soon)Intergration with Django Rest Framework (coming soon)How to installUsingpip:pip install django-cancanQuick startAddcancanto yourINSTALLED_APPSsetting like this:INSTALLED_APPS=[...,'cancan',]Create a function that define the access rules for a given user. For example, createabilities.pyinmyappmodule:defdefine_access_rules(user,rules):# Anybody can view published articlesrules.allow('view',Article,published=True)ifnotuser.is_authenticated:return# Allow logged in user to view his own articles, regardless of the `published` status# allow accepts the same kwargs that you would provide to QuerySet.filter methodrules.allow('view',Article,author=user)ifuser.has_perm('article.view_unpublished'):# You can also check for custom model permissions (i.e. view_unpublished)rules.allow('view',Article,published=False)ifuser.is_superuser:# Superuser gets unlimited access to all articlesrules.allow('add',Article)rules.allow('view',Article)rules.allow('change',Article)rules.allow('delete',Article)Insettings.pyaddCANCANsection, so thatcancanlibrary will know where to search fordefine_access_rulesfunction from the previous step:CANCAN={'ABILITIES':'myapp.abilities.define_access_rules'}Thedefine_access_rulesfunction will be executed automatically per each request by thecancanmiddleware. The middleware will call the function to determine the abilities of a current user.Let's addcancanmiddleware, just afterAuthenticationMiddleware:MIDDLEWARE=[...'django.contrib.auth.middleware.AuthenticationMiddleware','cancan.middleware.CanCanMiddleware',...]By adding the middleware you will also get an access torequest.abilityinstance which you can use
to:check model permissions,check object permissions,generate model querysets (i.e. when inheriting fromListView)Check for abilities in views:classArticleListView(ListView):model=Articledefget_queryset():# this is how you can retrieve all objects that current user can accessqs=self.request.ability.queryset_for('view',Article)returnqsclassArticleDetailView(PermissionRequiredMixin,DetailView):queryset=Article.objects.all()defhas_permission(self):article=self.get_object()# this is how you can check if user can access an objectreturnself.request.ability.can('view',article)Check for abilities in templatesYou can also check for abilities in template files, i. e. to show/hide/disable buttons or links.First you need to addcancanprocessor tocontext_processorsinTEMPLATESsection ofsettings.py:TEMPLATES=[{...,"OPTIONS":{"context_processors":[...,"cancan.context_processors.abilities",],},},]This will give you access toabilityobject in a template. You also need add{% load cancan_tags %}at the beginning
of the template file.Next you can check for object permissions:{% load cancan_tags %}

...

{% if ability|can:"change"|subject:article %}
    <a href="{% url 'article_edit' pk=article.id %}">Edit article</a>
{% endif %}or model permissions:{% if ability|can:"add"|"myapp.Article" %}
    <a href="{% url 'article_new' %}">Create new article</a>
{% endif %}You can also usecantemplate tag to create a reusable variable:{% can "add" "core.Project" as can_add_project %}
...
{% if can_add_project %}
    ...
{% endif %}Checking for abilities in Django Rest FrameworkLet's start by creating a pemission class:fromrest_frameworkimportpermissionsdefset_aliases_for_drf_actions(ability):"""map DRF actions to default Django permissions"""ability.access_rules.alias_action("list","view")ability.access_rules.alias_action("retrieve","view")ability.access_rules.alias_action("create","add")ability.access_rules.alias_action("update","change")ability.access_rules.alias_action("partial_update","change")ability.access_rules.alias_action("destroy","delete")classAbilityPermission(permissions.BasePermission):defhas_permission(self,request,view=None):ability=request.abilityset_aliases_for_drf_actions(ability)returnability.can(view.action,view.get_queryset().model)defhas_object_permission(self,request,view,obj):ability=request.abilityset_aliases_for_drf_actions(ability)returnability.can(view.action,obj)Next, secure the ViewSet withAbilityPermissionand overrideget_querysetmethod to list objects based on the access rights.classArticleViewset(ModelViewSet):permission_classes=[AbilityPermission]defget_queryset(self):returnself.request.ability.queryset_for(self.action,Article).distinct()ability.queryset_forandrules.allowexplainedWhen executingrules.allowyou specify 2 positional arguments:actionandsubject. Any additional parameters passed to allow will filter
the results in the same way as for DjangoQuerySet.fitermethod.Let's say that we have the following models incore.models.py:classProject(models.Model):name=models.CharField(max_length=128)description=models.TextField(default="",blank=True)members=models.ManyToManyField(User,through="Membership")created_by=models.ForeignKey(User,on_delete=models.CASCADE,related_name="owner")classMembership(models.Model):user=models.ForeignKey(User,on_delete=models.CASCADE)project=models.ForeignKey(Project,on_delete=models.CASCADE)If you have the following rules:rules.allow('view', Project, name="Foo")then executing:ability.queryset_for('view', Project)will result in the following query:SELECT "core_project"."id", "core_project"."name", "core_project"."description", "core_project"."created_by_id" FROM "core_project" WHERE "core_project"."name" = FooSimilarly,rules.allow('view', Project, name="Foo", description__contains="Bar")will generate a query:SELECT "core_project"."id", "core_project"."name", "core_project"."description", "core_project"."created_by_id" FROM "core_project" WHERE ("core_project"."description" LIKE %Bar% ESCAPE '\' AND "core_project"."name" = Foo)Multiple rules for the same action and model will result in OR'ed queries, i.e.:rules.allow('view', Project, name="Foo")
rules.allow('view', Project, description__contains="Bar")will generate a query:SELECT "core_project"."id", "core_project"."name", "core_project"."description", "core_project"."created_by_id" FROM "core_project" WHERE ("core_project"."description" LIKE %Bar% ESCAPE '\' OR "core_project"."name" = Foo)Seeexample_project/cancan_playground.ipynbfor more examples.SponsorsLogo made byFreepikfromwww.flaticon.com