Django REST Framework Utilities
Note
This page is for developers who want to customize or extend OpenWISP Users, whether for bug fixes, new features, or contributions.
For user guides and general information, please see:
This page details the Django REST Framework classes and utilities provided in the OpenWISP Users module. These tools support various REST API features such as authentication, permission enforcement, multi-tenancy, and filtering.
These utilities ensure consistency and reusability across the OpenWISP modules.
Authentication
openwisp_users.api.authentication.BearerAuthentication
BearerAuthentication
is the primary authentication class used in
OpenWISP's REST APIs. It is based on TokenAuthentication
from Django REST Framework.
For detailed usage instructions, please refer to the authenticating with the user token :ref:`authenticating_rest_api section.
openwisp_users.api.authentication.SesameAuthentication
SesameAuthentication
allows authentication using tokens generated by
django-sesame.
This method is primarily used for password-less authentication, such as magic login links sent via email or SMS.
To use this authentication class, you must configure django-sesame
.
For more details, please see the django-sesame documentation.
Permission Classes
The custom Django REST Framework permission classes
IsOrganizationMember
, IsOrganizationManager
, and
IsOrganizationOwner
ensure that the requesting user belongs to the
same organization as the requested object and has the appropriate role:
member, manager, or owner, respectively.
Usage example:
from openwisp_users.api.permissions import IsOrganizationManager
from rest_framework import generics
class MyApiView(generics.APIView):
permission_classes = (IsOrganizationManager,)
organization_field
type: |
|
default: |
|
organization_field
specifies where to find the organization of the
current object. In most cases, this default value does not need to be
changed. However, it may need to be adjusted if the organization
is
defined only on a parent object.
For example, in openwisp-firmware-upgrader, the organization
is defined on
Category
, and Build
has a relation to Category
. Therefore, the
organization of Build
instances is inferred from the Category
organization.
To implement the permission class correctly in such cases, you would use:
from openwisp_users.api.permissions import IsOrganizationManager
from rest_framework import generics
class MyApiView(generics.APIView):
permission_classes = (IsOrganizationManager,)
organization_field = "category__organization"
This setup translates to accessing obj.category.organization
. Ensure
your view's querysets use select_related
to avoid generating too many queries.
DjangoModelPermissions
The default DjangoModelPermissions
class does not check for the
view
permission on objects for GET
requests. The extended
DjangoModelPermissions
class addresses this issue. It checks for the
availability of either the view
or change
permissions to allow
GET
requests on any object.
Usage example:
from openwisp_users.api.permissions import DjangoModelPermissions
from rest_framework.generics import ListCreateAPIView
class TemplateListCreateView(ListCreateAPIView):
serializer_class = TemplateSerializer
permission_classes = (DjangoModelPermissions,)
queryset = Template.objects.all()
Note: DjangoModelPermissions
allows users who are either
organization managers or owners to view shared objects in read-only mode.
Standard users will not be able to view or list shared objects.
ProtectedAPIMixin
Full python path: openwisp_users.api.mixins.ProtectedAPIMixin
.
This mixin provides a set of authentication and permission classes that are commonly used across various OpenWISP modules API views.
Usage example:
# Used in openwisp-ipam
from openwisp_users.api.mixins import (
ProtectedAPIMixin as BaseProtectedAPIMixin,
)
class ProtectedAPIMixin(BaseProtectedAPIMixin):
throttle_scope = "ipam"
class SubnetView(ProtectedAPIMixin, RetrieveUpdateDestroyAPIView):
serializer_class = SubnetSerializer
queryset = Subnet.objects.all()
Mixins for Multi-Tenancy
Filtering Items by Organization
The custom Django REST Framework mixins
FilterByOrganizationMembership
, FilterByOrganizationManaged
and
FilterByOrganizationOwned
can be used in the API views to ensure that
the current user is able to see only the data related to their
organization when accessing the API view.
These classes work by filtering the queryset so that only items related to organizations the user is member, manager or owner of, respectively.
These mixins ship the Django REST Framework's IsAuthenticated
permission class by default because the organization filtering works only
on authenticated users. Always remember to include this class when
overriding permission_classes
in a view.
Usage example:
from openwisp_users.api.mixins import FilterByOrganizationManaged
from rest_framework import generics
class UsersListView(FilterByOrganizationManaged, generics.ListAPIView):
"""
UsersListView will show only users from organizations managed
by current user in the list.
"""
pass
class ExampleListView(FilterByOrganizationManaged, generics.ListAPIView):
"""
Example showing how to extend ``permission_classes``.
"""
permission_classes = FilterByOrganizationManaged.permission_classes + [
# additional permission classes here
]
Checking Parent Objects
Sometimes, the API view needs to check the existence and the
organization
field of a parent object.
In such cases, FilterByParentMembership
, FilterByParentManaged
and
FilterByParentOwned
can be used.
For example, given a hypothetical URL
/api/v1/device/{device_id}/config/
, the view must check that
{device_id}
exists and that the user has access to it, here's how to
do it:
import swapper
from rest_framework import generics
from openwisp_users.api.mixins import FilterByParentManaged
Device = swapper.load_model("config", "Device")
Config = swapper.load_model("config", "Config")
# URL is:
# /api/v1/device/{device_id}/config/
class ConfigListView(FilterByParentManaged, generics.DetailAPIView):
model = Config
def get_parent_queryset(self):
qs = Device.objects.filter(pk=self.kwargs["device_id"])
return qs
Multi-tenant Serializers for the Browsable Web UI
Django REST Framework provides a browsable API which can be used to create HTTP requests right from the browser.
The relationship fields in this interface show all the relationships, without filtering by the organization the user has access to, which breaks multi-tenancy.
The FilterSerializerByOrgMembership
, FilterSerializerByOrgManaged
and FilterSerializerByOrgOwned
can be used to solve this issue.
These serializers do not allow non-superusers to create shared objects.
Usage example:
from openwisp_users.api.mixins import FilterSerializerByOrgOwned
from rest_framework.serializers import ModelSerializer
from .models import Device
class DeviceSerializer(FilterSerializerByOrgOwned, ModelSerializer):
class Meta:
model = Device
fields = "__all__"
The include_shared
boolean attribute can be used to include shared
objects in the accepted values of the multi-tenant serializers.
Shared objects have the organization
field set to None
and can be
used by any organization. A common use case is shared templates in
OpenWISP Controller.
Usage example:
from openwisp_users.api.mixins import FilterSerializerByOrgOwned
from rest_framework.serializers import ModelSerializer
from .models import Book
class BookSerializer(FilterSerializerByOrgOwned, ModelSerializer):
include_shared = True
class Meta:
model = Book
fields = "__all__"
To filter items based on the organization
of their parent object,
organization_field
attribute can be defined in the view function which
is inheriting any of the mixin classes.
Usage example: organization_field.
Multi-tenant Filtering Capabilities for the Browsable Web UI
Integration of Django filters
with Django REST Framework is
provided through a DRF-specific FilterSet
and a filter backend
.
The relationship fields of django-filters
show all the available
results, without filtering by the organization the user has access to,
which breaks multi-tenancy.
The FilterDjangoByOrgMembership
, FilterDjangoByOrgManaged
and
FilterDjangoByOrgOwned
can be used to solve this issue.
Usage example:
from django_filters import rest_framework as filters
from openwisp_users.api.mixins import FilterDjangoByOrgManaged
from ..models import FloorPlan
class FloorPlanOrganizationFilter(FilterDjangoByOrgManaged):
organization_slug = filters.CharFilter(
field_name="organization__slug"
)
class Meta:
model = FloorPlan
fields = ["organization", "organization_slug"]
class FloorPlanListCreateView(
ProtectedAPIMixin, generics.ListCreateAPIView
):
serializer_class = FloorPlanSerializer
queryset = FloorPlan.objects.select_related().order_by("-created")
pagination_class = ListViewPagination
filter_backends = [filters.DjangoFilterBackend]
filterset_class = FloorPlanOrganizationFilter
You can also use the organization filter classes such as
OrganizationManagedFilter
from openwisp_users.api.filters
which
includes organization
and organization_slug
filter fields by
default.
Usage example:
from django_filters import rest_framework as filters
from openwisp_users.api.filters import OrganizationManagedFilter
from ..models import FloorPlan
class FloorPlanFilter(OrganizationManagedFilter):
class Meta(OrganizationManagedFilter.Meta):
model = FloorPlan
class FloorPlanListCreateView(
ProtectedAPIMixin, generics.ListCreateAPIView
):
serializer_class = FloorPlanSerializer
queryset = FloorPlan.objects.select_related().order_by("-created")
pagination_class = ListViewPagination
filter_backends = [filters.DjangoFilterBackend]
filterset_class = FloorPlanFilter