Quickstart

Add django-caps to installed apps and add middleware:

Setup

In settings, add application and middleware. The middleware is used to assign user’s Agent to the request.

# settings.py
INSTALLED_APPS = [
    "caps"
    # add "caps.tests.apps" for development
    # ...
]

MIDDLEWARE = [
    # we requires AuthenticationMiddleware as dependency
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "caps.middleware.AgentMiddleware",
    # ...
]

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "caps.backends.PermissionsBackend",
]

Create an object to be accessed:

# models.py
from django.db import models
from django.utils.translation import gettext_lazy as _

from caps.models import Owned

__all__ = ("Post",)


# Create our example model. A Access and Capability model will be
# generated and accessible from Post (Post.Access, Post.Capability)
class Post(Owned):
    title = models.CharField(_("Title"), max_length=64)
    content = models.TextField(_("Content"))
    created_at = models.DateTimeField(_("Date of creation"), auto_now_add=True)

    # Allowed permissions with allowed reshare depth
    root_grants = {
        "app.view_post": 2, # can be shared then reshared
        "app.change_post": 1, # can be shared once
        "app.delete_post": 0, # can not be shared
    }

View and urls

Using views provided by caps, example of urls.py file:

from django.urls import path

from caps import views
from . import models

urlpatterns = [
    path("/post/", views.OwnedOwnedistView.as_view(model=models.Post), name="post-list"),
    path("/post/<uuid:uuid>/", views.OwnedOwnedetailView.as_view(model=models.Post), name="post-detail"),
    path("/post/create/", views.OwnedOwnedreateView.as_view(model=models.Post), name="post-create"),
    path(
        "/post/update/<uuid:uuid>",
        views.OwnedUpdateView.as_view(model=models.Post),
        name="post-update",
    ),
]

Even a shorter version, providing views for object accesses too:

from caps import urls
from . import models

# By settings `accesses=True` you also add default views for accesses assuming related templates exists (such as `myapp/postaccess_detail.html`).
urlpatterns = urls.get_object_paths(models.Post, 'post', accesses=True)

You can have custom views as:

from caps import views
from . import models, serializers

__all__ = ("PostDetailView",)


class PostDetailView(views.OwnedDetailView):
    model = models.Post

    # do something here...

Provided views

Although we provide basic views for django-caps’ models, we don’t provide template, and it will be up to you to write them according Django practices.

We have views for the following models:

API

This is simple too, in viewsets.py:

from caps import views
from . import models, serializers

__all__ = ('PostViewSet', 'PostAccessViewSet')

# Example of viewset using DRF.
# assuming you have implemented serializer for Post
class PostViewSet(viewsets.OwnedViewSet):
    model = models.Post
    queryset = models.Post.objects.all()
    serializer_class = serializers.PostSerializer

class PostAccessViewSet(viewsets.AccessViewSet):
    model = models.Post.Access
    queryset = models.Post.Access.objects.all()

Serializers:

from rest_framework import serializers
from caps.serializers import OwnedSerializer

from . import models

__all__ = ('PostSerializer',)

class PostSerializer(OwnedSerializer, serializers.ModelSerializer):
    class Meta:
        model = models.Post
        fields = OwnedSerializer.fields + ('title', 'content', 'created_at')

You’ll have to manually add routes and urls for this part:

from django.urls import path
from rest_framework.routers import SimpleRouter

from . import viewsets

router = SimpleRouter()
router.register('post', viewsets.PostViewSet)
router.register('post-access', viewsets.PostAccessViewSet)

urlpatterns = [
    # ...
    path('api', include(router.urls)
]

Some example usage

Example of Django-Caps’ API usage:

from datetime import timedelta

from django.contrib.auth.models import User, Permission
from django.utils import timezone as tz

from caps.models import Agent
from .models import Post

# We assume the users already exists
user = User.objects.all()[0]
user_1 = User.objects.all()[1]

# Create agents (this is handled by middleware).
agent = Agent.objects.create(user=user)
agent_1 = Agent.objects.create(user=user_1)

# Create the post
post = Post.objects.create(owner=agent, title="Some title", content="Some content")

# Share the post to agent 1 with default grants
access = post.share(agent_1)
assert access.grants == {"app.view_post": 1, "app.change_post": 0}

# Or with an expiration datetime
access = post.share(agent_1, expiration=tz.now() + timedelta(hours=2))

# Lets imagine there is another agent called agent_2
# Lets try to escalade privilege...
access_2 = access.share(agent_2, {"app.view_post": 2, "app.change_post": 1})
assert access_2.grants == {"app.view_post": 0}