A tidy and extendable way of defining access requirements for views. Because mixins and decorators gets messy.
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
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
.
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:
- Check if
LoggedIn.is_fulfilled()
isTrue
. - If not, make the view return
LoggedIn.not_fulfilled()
and stop. - Otherwise, check if
Active.is_fulfilled()
isTrue
- If not, make the view return
Active.not_fulfilled()
and stop. - Otherwise continue to execute the view as normal.
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'
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"
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.
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
Install test requirements:
$ pip install -e .[test]
Run tests:
$ make test
django-access-tools is licensed under The MIT License (MIT). See LICENSE file for more information.