Skip to main content

Syntactic sugar for handling permission functions in views, templates, and code

Project description

# Django Perms

[![Build Status](https://travis-ci.org/PSU-OIT-ARC/django-perms.svg?branch=develop)](https://travis-ci.org/PSU-OIT-ARC/django-perms)

`django-perms` provides a simple way to add authorization checks to
Django views. It takes a different approach to authorization compared to
packages like `django-guardian`. `django-perms` doesn't store any
permission state in the database. Instead, you define functions that
return `True` or `False` depending on whether the user is authorized to
do something. You are free to implement these functions however you see
fit. This allows for coarse permissions such as `user.is_staff` and more
fine-grained permissions based on, e.g., whether a user has access to
a specific model instance.

## Install

Add `'django-perms'` to `install_requires` and/or to `requirements.txt`.

Or install it directly with pip:

pip install django-perms

If you want to use permissions template filters, add `'permissions'` to
your project's `INSTALLED_APPS` setting.

## Usage

Create a module named `perms` in the top level of your Django project.
In this module, create a permissions registry:

# project/package/perms.py
from permissions import PermissionsRegistry
permissions = PermissionsRegistry()

NOTE: You can create the permissions registry anywhere; creating it in
a module name `perms` is just a convention.

If you have some permissions that will be useful across more than one of
your Django apps, the top level `perms` module is a good place to define
them:

@permissions.register
def is_staff(user):
return user.is_staff

The `is_staff` permission can be applied to a view like so:

from package.perms import permissions

@permissions.required('is_staff')
def some_view_only_accessible_by_staff(request):
pass # Do important stuff

Note that all permissions functions *must* take a User model object as
their first argument.

App-specific permissions are typically defined in a module named `perms`
in the app's package. For example, let's say you have a `widgets` app in
your project, then you might define some permissions like so:

# project/package/widgets/perms.py
from package.perms import permissions

@permissions.register
def can_create_widget(user):
if user.is_staff:
return True
num_widgets = Widget.objects.filter(created_by=user).count()
return num_widgets <= MAX_WIDGETS

@permissions.register(model=Widget)
def can_edit_widget(user, widget):
if user.is_staff:
return True
return user == widget.owner

@permissions.register(model=Widget, allow_anonymous=True)
def can_view_widget(user, widget):
if user.is_staff:
return True
if user.is_anonymous():
return widget.is_public
return user == widget.owner

Those widget permissions can be applied to your widget views like this:

# project/package/widgets/views.py
from django.http import HttpResponse
from package.perms import permissions
from .models import Widget

@permissions.require('can_create_widget')
def create_widget(request):
# Create a widget here
return HttpResponse('Widget created')

@permissions.require('can_edit_widget', model=Widget)
def edit_widget(request, widget_id):
widget = get_object_or_404(Widget, pk=widget_id)
# Edit the widget here
return HttpResponse('Edited %s' % widget)

# If you want to look up widgets by a field other than the primary
# key field, you can specify the `field` option:
@permissions.require('can_edit_widget', field='name')
def edit_widget(request, name):
widget = get_object_or_404(Widget, name=name)
return HttpResponse('Edited %s' % widget)

If you're using class-based views, you can apply a permission to a class
or to a method:

@permissions.require('xyz')
class MyView(View):

# All methods require the 'xyz' permission.

def get(request):
pass

def post(request):
pass

class MyOtherView(View):

# Only the post method requires the 'xyz' permission. Other
# methods are unprotected.

def get(request):
pass

@permissions.require('xyz')
def post(request):
pass

When a permission is registered, a corresponding template filter is also
created. Given the permissions registered above, you can do this in your
templates:

{% load permissions %}

{% if user|is_staff %}
Hello, staff user.
{% endif %}

{% if user|can_create_widget %}
You can create widgets!
{% endif %}

{% if user|can_edit_widget:widget %}
You can edit this widget!
{% endif %}

## Permissions Registered with a Model

When registering a permission that operates on a model, it's assumed
that the second argument of any view function requiring that permission
is the model lookup field (typically a primary key):

@permissions.register(model=Fruit)
def can_eat_fruit(user, fruit):
return not user.is_allergic_to(fruit)

@permissions.require('can_eat_fruit')
def consume_fruit_view(request, fruit_id):
pass # Consume the fruit if allowed

The `fruit_id` passed to the `consume_fruit_view` will be used by the
permissions machinery to load a `Fruit` object, which will be passed
into the `can_eat_fruit` permission function.

When using class-based views, the `self` arg is skipped when looking for
the lookup field.

## Allowing Staff and/or Superusers Access to All Views by Default

If you find yourself writing `if user.is_staff: return True` at the top
of most or all your permission functions, you can allow staff permission
to access all views by default:

permissions = PermissionsRegistry(is_staff=True)

If you have a view that shouldn't be accessible to staff for some
reason, you can override the default `is_staff` setting on a per-
permission basis:

@permissions.register(is_staff=False)
def is_superuser(user):
return user.is_superuser

There's an analogous `is_superuser` option.

## Anonymous Users

By default, anonymous users will be redirected to the login page. In
some cases, it may make sense to give anonymous users a chance to access
certain views.

For example, if you have an `Article` model with an `is_published`
field, you may want to allow anonymous users to access articles that
have been published. To allow this, you'd define and require a
permission like this:

# perms.py
@permissions.register(model=Article, allow_anonymous=True)
def can_view_article(user, article):
if article.is_published:
return True
if user.is_anonymous():
return False # Redirect to login page
return user == article.owner

# views.py
@permissions.require('can_view_article')
def article_view(request, article_id):
pass # Show article

If the permission check fails for an anonymous user, they will be
redirected to the login page.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-perms-1.4.0.tar.gz (14.9 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page