django-access-tools

An abstract access manager for Django

View the Project on GitHub antonagestam/django-access-tools

Django Access Tools

Build Status Coverage Status

A tidy and extendable way of defining access requirements for views. Because mixins and decorators gets messy.

Installation

Install using pip:

pip install django-access-tools

Or latest version in repo:

pip install -e git+https://github.com/antonagestam/django-access-tools/#egg=access

Usage

Requirements

Access requirements are specified by extending the Requirement class. The is_fulfilled method is what defines your logic of when the requirement is fulfilled. By overriding not_fulfilled you specify what should happen if the requirement is not fulfilled. For example this simple LoggedIn requirement:

from django.http import Http404
from access.requirements import Requirement


class LoggedIn(Requirement):
    def is_fulfilled(self):
        return self.request.user.is_authenticated()

    def not_fulfilled(self):
        return Http404()

Requirement.request: Request object. Gets set by Requirement.setup.

Requirement.args: Request arguments passed to the view. Gets set by Requirement.setup.

Requirement.kwargs: Request keyword arguments passed to the view. Gets set by Requirement.setup.

Views

Access requirements for a view will be evaluated in the order they're specified. For example access_requirements = [LoggedIn, Active] will have this chain of events before the view is executed:

Class-based Views

Extend your views with ManagedAccessViewMixin and specify access_requirements:

from django.views.generic import TemplateView
from access.views import ManagedAccessViewMixin
from access.requirements import Active, LoggedIn


class MyView(ManagedAccessViewMixin, TemplateView):
    access_requirements = [LoggedIn, Active]
    template = 'index.html'

Functional Views

For functional views, use Requirement.as_decorator.

from access.requirements import LoggedIn

@LoggedIn.decorator
def my_view(request):
    return "Hello world"

When combining many requirements for a functional view, it's recommended to use access_requirements. It returns a decorator and takes requirements as positional arguments.

from access.decorators import access_requirements
from access.requirements import Active, LoggedIn

@access_requirements(LoggedIn, Active)
def my_view(request):
    return "Hello world"

Built-in Requirements

PageNotFoundRequirement(Requirement): Raises Http404() if unfulfilled.

Staff(PageNotFoundRequirement): Raises Http404() if user is not staff.

SuperUser(PageNotFoundRequirement): Raises Http404() if user is not superuser.

Active(PageNotFoundRequirement): Raises Http404() if user is not active.

RedirectRequirement(Requirement): Returns Http307(self.get_url()) if not fulfilled. Specify url_name or override get_url to set URL to redirect to. Appends the current URL as ?next=current_url by default, set append_next = False to prevent this.

LoggedIn(RedirectRequirement): Returns Http307('login') if user is not authenticated.

More Advanced Usage Example

Let's say you have a view where the user should only be allowed access if they've accepted your terms of service and confirmed their email address.

This example redirects the user to different views depending on if they've accepted the terms of service and confirmed their email. RedirectRequirement appends ?next={url} to the redirect URLs so that those views can redirect the user back after completing the steps.

from access.requirements import RedirectRequirement


class ProfileFieldRequirement(RedirectRequirement):
    profile_field_name = None

    def __init__(self, *args, **kwargs):
        self.required_field_value = kwargs.pop('required_field_value', True)
        super(ProfileFieldRequirement, self).__init__(*args, **kwargs)

    def is_fulfilled(self):
        if self.profile_field_name is None:
            raise ImproperlyConfigured(
                "ProfileFieldRequirements need to specify "
                "`profile_field_name`.")
        value = getattr(self.request.user.profile, self.profile_field_name)
        return value == self.required_field_value


class AcceptedTerms(ProfileFieldRequirement):
    url_name = 'accept_tos'
    profile_field_name = 'accepted_terms'


class ConfirmedEmail(ProfileFieldRequirement):
    url_name = 'prompt_email'
    profile_field_name = 'confirmed_email'

# … in your views.py:

from access.views import ManagedAccessViewMixin


class MyView(ManagedAccessViewMixin, View):
    access_requirements = [AcceptedTerms, ConfirmedEmail]

    # … view code

Run tests

Install test requirements:

$ pip install -r test-requirements.txt

Run tests:

$ make test

License

django-access-tools is licensed under The MIT License (MIT). See LICENSE file for more information.