{"id":20354778,"url":"https://github.com/pgorecki/django-cancan","last_synced_at":"2025-11-08T05:04:29.808Z","repository":{"id":47122730,"uuid":"290995818","full_name":"pgorecki/django-cancan","owner":"pgorecki","description":"🔓Authorization library for Django","archived":false,"fork":false,"pushed_at":"2022-03-16T11:48:27.000Z","size":133,"stargazers_count":39,"open_issues_count":1,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-12T02:36:58.405Z","etag":null,"topics":["authorization","django","permissions"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pgorecki.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-28T08:40:02.000Z","updated_at":"2025-01-13T13:22:53.000Z","dependencies_parsed_at":"2022-09-08T06:02:44.520Z","dependency_job_id":null,"html_url":"https://github.com/pgorecki/django-cancan","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgorecki%2Fdjango-cancan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgorecki%2Fdjango-cancan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgorecki%2Fdjango-cancan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgorecki%2Fdjango-cancan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pgorecki","download_url":"https://codeload.github.com/pgorecki/django-cancan/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248507019,"owners_count":21115522,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","django","permissions"],"created_at":"2024-11-14T23:09:41.103Z","updated_at":"2025-11-08T05:04:29.751Z","avatar_url":"https://github.com/pgorecki.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# django-cancan\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"django-cancan.svg\" alt=\"Logo\" height=\"224\" /\u003e\n\u003c/p\u003e\n\n[![Build Status](https://travis-ci.com/pgorecki/django-cancan.svg?branch=master)](https://travis-ci.com/pgorecki/django-cancan)\n[![PyPI version](https://badge.fury.io/py/django-cancan.svg)](https://badge.fury.io/py/django-cancan)\n\n`django-cancan` is 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.\n\nThis library is inspired by [cancancan](https://github.com/CanCanCommunity/cancancan) for Ruby on Rails.\n\n## Key features\n\n- All of your permissions logic is kept in one place. User permissions are defined in a single function and not scattered across views,\n  querysets, etc.\n\n- Same permissions logic is used to check permissions on a single model instance and to generate queryset containing all instances that the user can access\n\n- Easy unit testing\n\n- Integration with built-in Django default permissions system and Django admin (coming soon)\n\n- Intergration with Django Rest Framework (coming soon)\n\n## How to install\n\nUsing `pip`:\n\n```\npip install django-cancan\n```\n\n## Quick start\n\n1. Add `cancan` to your `INSTALLED_APPS` setting like this:\n\n```python\nINSTALLED_APPS = [\n    ...,\n    'cancan',\n]\n```\n\n2. Create a function that define the access rules for a given user. For example, create `abilities.py` in `myapp` module:\n\n```python\ndef define_access_rules(user, rules):\n    # Anybody can view published articles\n    rules.allow('view', Article, published=True)\n\n    if not user.is_authenticated:\n        return \n\n    # Allow logged in user to view his own articles, regardless of the `published` status\n    # allow accepts the same kwargs that you would provide to QuerySet.filter method\n    rules.allow('view', Article, author=user)\n\n    if user.has_perm('article.view_unpublished'):\n        # You can also check for custom model permissions (i.e. view_unpublished)\n        rules.allow('view', Article, published=False)\n    \n\n    if user.is_superuser:\n        # Superuser gets unlimited access to all articles\n        rules.allow('add', Article)\n        rules.allow('view', Article)\n        rules.allow('change', Article)\n        rules.allow('delete', Article)\n```\n\n3.  In `settings.py` add `CANCAN` section, so that `cancan` library will know where to search for `define_access_rules` function from the previous step:\n\n```python\nCANCAN = {\n    'ABILITIES': 'myapp.abilities.define_access_rules'\n}\n```\n\nThe `define_access_rules` function will be executed automatically per each request by the `cancan` middleware. The middleware will call the function to determine the abilities of a current user.\n\nLet's add `cancan` middleware, just after `AuthenticationMiddleware`:\n\n```python\nMIDDLEWARE = [\n    ...\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'cancan.middleware.CanCanMiddleware',\n    ...\n]\n```\n\nBy adding the middleware you will also get an access to `request.ability` instance which you can use\nto: \n - check model permissions, \n - check object permissions,\n - generate model querysets (i.e. when inheriting from `ListView`)\n\n4. Check for abilities in views:\n\n```python\n\nclass ArticleListView(ListView):\n    model = Article\n\n    def get_queryset(self):\n        # this is how you can retrieve all objects that current user can access\n        qs = self.request.ability.queryset_for('view', Article)\n        return qs\n\n\nclass ArticleDetailView(PermissionRequiredMixin, DetailView):\n    queryset = Article.objects.all()\n\n    def has_permission(self):\n        article = self.get_object()\n        # this is how you can check if user can access an object\n        return self.request.ability.can('view', article)\n```\n\n5. Check for abilities in templates\n\nYou can also check for abilities in template files, i. e. to show/hide/disable buttons or links.\n\nFirst you need to add `cancan` processor to `context_processors` in `TEMPLATES` section of `settings.py`:\n\n```python\nTEMPLATES = [\n    {\n        ...,\n        \"OPTIONS\": {\n            \"context_processors\": [\n                ...,\n                \"cancan.context_processors.abilities\",\n            ],\n        },\n    },\n]\n```\n\nThis will give you access to `ability` object in a template. You also need add `{% load cancan_tags %}` at the beginning \nof the template file.\n\nNext you can check for object permissions:\n\n```\n{% load cancan_tags %}\n\n...\n\n{% if ability|can:\"change\"|subject:article %}\n    \u003ca href=\"{% url 'article_edit' pk=article.id %}\"\u003eEdit article\u003c/a\u003e\n{% endif %}\n```\n\nor model permissions:\n\n```\n{% if ability|can:\"add\"|subject:\"myapp.Article\" %}\n    \u003ca href=\"{% url 'article_new' %}\"\u003eCreate new article\u003c/a\u003e\n{% endif %}\n```\n\nYou can also use `can` template tag to create a reusable variable:\n\n```\n{% can \"add\" \"core.Project\" as can_add_project %}\n...\n{% if can_add_project %}\n    ...\n{% endif %}\n```\n\n## Checking for abilities in Django Rest Framework\n\nLet's start by creating a pemission class:\n\n```python\nfrom rest_framework import permissions\n\ndef set_aliases_for_drf_actions(ability):\n    \"\"\"\n    map DRF actions to default Django permissions\n    \"\"\"\n    ability.access_rules.alias_action(\"list\", \"view\")\n    ability.access_rules.alias_action(\"retrieve\", \"view\")\n    ability.access_rules.alias_action(\"create\", \"add\")\n    ability.access_rules.alias_action(\"update\", \"change\")\n    ability.access_rules.alias_action(\"partial_update\", \"change\")\n    ability.access_rules.alias_action(\"destroy\", \"delete\")\n\n\nclass AbilityPermission(permissions.BasePermission):\n    def has_permission(self, request, view=None):\n        ability = request.ability\n        set_aliases_for_drf_actions(ability)\n        return ability.can(view.action, view.get_queryset().model)\n\n    def has_object_permission(self, request, view, obj):\n        ability = request.ability\n        set_aliases_for_drf_actions(ability)\n        return ability.can(view.action, obj)\n```\n\nNext, secure the ViewSet with `AbilityPermission` and override `get_queryset` method to list objects based on the access rights.\n\n```python\nclass ArticleViewset(ModelViewSet):\n    permission_classes = [AbilityPermission]\n\n    def get_queryset(self):\n        return self.request.ability.queryset_for(self.action, Article).distinct()\n```\n\n## Itegrating with admin panel\n\nTo inegrate `django-cancan` with the admin panel, add the following mixin to your `admin.ModelAdmin` class.\n\n```\nclass AbilityModelAdminMixin:\n    def get_queryset(self, request):\n        if request.user.is_superuser:\n            return super().get_queryset(request)\n        return request.ability.queryset_for(\"view\", self.model)\n\n    def has_module_permission(self, request):\n        if request.user.is_superuser:\n            return True\n\n        can = request.ability.can\n        return (\n                can(\"view\", self.model)\n                or can(\"change\", self.model)\n                or can(\"delete\", self.model)\n        )\n\n    def has_add_permission(self, request, obj=None):\n        if request.user.is_superuser:\n            return True\n        return request.ability.can(\"add\", self.model)\n\n    def has_view_permission(self, request, obj=None):\n        if request.user.is_superuser:\n            return True\n        return request.ability.can(\"view\", self.model)\n\n    def has_change_permission(self, request, obj=None):\n        if request.user.is_superuser:\n            return True\n        return request.ability.can(\"change\", self.model)\n\n    def has_delete_permission(self, request, obj=None):\n        if request.user.is_superuser:\n            return True\n        return request.ability.can(\"delete\", self.model)\n```\n\nlike so:\n\n```\nclass AbilityModelAdmin(AbilityModelAdminMixin, admin.ModelAdmin):\n    pass\n    \nadmin.site.register(Article, AbilityModelAdmin)\n```\n\n## Unit testing\n\nThis is how you can unit test your `define_access_rules` function.\n\n```python\nfrom cancan.access_rules import AccessRules\nfrom cancan.ability import Ability\nfrom myapp.abilities import define_access_rules\n\nuser = somehow_create_user(...)\ninstance1 = MyModel.objects.create(...)\n\naccess_rules = AccessRules(user)\ndefine_access_rules(user1, access_rules)\nability = Ability(access_rules)\n\nassert instance1 in ability.queryset_for(\"view\", MyModel)\nassert ability.can(\"update\", instance1)\n```\n\n## `ability.queryset_for` and `rules.allow` explained\n\nWhen executing `rules.allow` you specify 2 positional arguments: `action` and `subject`. Any additional parameters passed to allow will filter\nthe results in the same way as for Django `QuerySet.fiter` method.\n\nLet's say that we have the following models in `core.models.py`:\n\n```python\nclass Project(models.Model):\n    name = models.CharField(max_length=128)\n    description = models.TextField(default=\"\", blank=True)\n    members = models.ManyToManyField(User, through=\"Membership\")\n    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name=\"owner\")\n\nclass Membership(models.Model):\n    user = models.ForeignKey(User, on_delete=models.CASCADE)\n    project = models.ForeignKey(Project, on_delete=models.CASCADE)\n```\n\nIf you have the following rules:\n```\nrules.allow('view', Project, name=\"Foo\")\n```\n\nthen executing:\n```\nability.queryset_for('view', Project)\n```\n\nwill result in the following query:\n```\nSELECT \"core_project\".\"id\", \"core_project\".\"name\", \"core_project\".\"description\", \"core_project\".\"created_by_id\" FROM \"core_project\" WHERE \"core_project\".\"name\" = Foo\n```\n\nSimilarly, `rules.allow('view', Project, name=\"Foo\", description__contains=\"Bar\")`\n\nwill generate a query:\n```\nSELECT \"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)\n```\n\nMultiple rules for the same action and model will result in OR'ed queries, i.e.:\n```\nrules.allow('view', Project, name=\"Foo\")\nrules.allow('view', Project, description__contains=\"Bar\")\n```\n\nwill generate a query:\n```\nSELECT \"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)\n```\n\nSee [example_project/cancan_playground.ipynb](example_project/cancan_playground.ipynb) for more examples.\n\n\n## Sponsors\n\n\n\u003ca href=\"https://www.stxnext.com/\" target=\"_blank\"\u003e\n  \u003cimg src=\"https://4542168.fs1.hubspotusercontent-na1.net/hubfs/4542168/STXNext_logo_GreenGradient@3x%201.png\" alt=\"STX Next\" /\u003e\n\u003c/a\u003e\n\u003cbr\u003e\n\u003ca href=\"https://ermlab.com/\" target=\"_blank\"\u003e\n  \u003cimg src=\"https://ermlab.com/wp-content/uploads/2019/08/ermlab_logo_plain_h80.png\" alt=\"Ermlab\" width=\"200\" style=\"position: relative;left: -23px;\"/\u003e\n\u003c/a\u003e\n\n\u003chr\u003e\n\n\u003cdiv\u003eLogo made by \u003ca href=\"http://www.freepik.com/\" title=\"Freepik\"\u003eFreepik\u003c/a\u003e from \u003ca href=\"https://www.flaticon.com/\" title=\"Flaticon\"\u003ewww.flaticon.com\u003c/a\u003e\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpgorecki%2Fdjango-cancan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpgorecki%2Fdjango-cancan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpgorecki%2Fdjango-cancan/lists"}