All Projects → drf-psq → drf-psq

drf-psq / drf-psq

Licence: MIT License
The simplest and most general way to manage action-based permissions, serializers, and querysets dependent on permission-based rules for the Django REST framework!

Programming Languages

python
139335 projects - #7 most used programming language

Projects that are alternatives of or similar to drf-psq

python-web-dev-21-2
Material for "Web Development in Python with Django" using Django 2.1, published as a Pearson LiveLesson on Safari Books Online
Stars: ✭ 38 (+58.33%)
Mutual labels:  django-rest-framework, django-extensions
react pyconlunch
Companion React Native app for Pycon Lunch app
Stars: ✭ 80 (+233.33%)
Mutual labels:  django-rest-framework
django-rest-framework-yaml
YAML support for Django REST Framework
Stars: ✭ 27 (+12.5%)
Mutual labels:  django-rest-framework
jasmin-web-panel
📨 Jasmin Web Panel for Jasmin SMS Gateway
Stars: ✭ 33 (+37.5%)
Mutual labels:  django-rest-framework
kamu
You favorite book library
Stars: ✭ 65 (+170.83%)
Mutual labels:  django-rest-framework
django-rest-framework-roles
Parameterizes Django REST Framework methods over user-defined roles
Stars: ✭ 132 (+450%)
Mutual labels:  django-rest-framework
GSoC-Data-Analyser
Simple search for organisations participating/participated in the GSoC
Stars: ✭ 29 (+20.83%)
Mutual labels:  django-rest-framework
indrz-be
Indoor mapping, routing system for orientation and wayfinding or facility management
Stars: ✭ 80 (+233.33%)
Mutual labels:  django-rest-framework
network-pipeline
Network traffic data pipeline for real-time predictions and building datasets for deep neural networks
Stars: ✭ 36 (+50%)
Mutual labels:  django-rest-framework
openverse-api
The Openverse API allows programmatic access to search for CC-licensed and public domain digital media.
Stars: ✭ 41 (+70.83%)
Mutual labels:  django-rest-framework
django-rest-framework-aggregates
Exposes aggregation features of the Django model queryset to the DRF API.
Stars: ✭ 23 (-4.17%)
Mutual labels:  django-rest-framework
instagram-api-clone
Instagram RESTful API clone made with Django REST framework
Stars: ✭ 56 (+133.33%)
Mutual labels:  django-rest-framework
django-api-bouncer
Simple Django app to provide API Gateways for micro-services
Stars: ✭ 18 (-25%)
Mutual labels:  django-rest-framework
lego
LEGO Backend
Stars: ✭ 48 (+100%)
Mutual labels:  django-rest-framework
journal-manager
EteSync - server django app
Stars: ✭ 34 (+41.67%)
Mutual labels:  django-rest-framework
elearning
e-learning django app (django, python)
Stars: ✭ 107 (+345.83%)
Mutual labels:  django-rest-framework
django-tutorial
Django 4 tutorial projects
Stars: ✭ 11 (-54.17%)
Mutual labels:  django-rest-framework
platform
API for the Penn Labs platform built using Django REST framework. Includes accounts engine, club directory, product listings, documentation etc.
Stars: ✭ 20 (-16.67%)
Mutual labels:  django-rest-framework
django-restful-admin
Django admin restful api
Stars: ✭ 51 (+112.5%)
Mutual labels:  django-rest-framework
shopping-cart
A simple Shopping-cart built with React and Django REST Framework(DRF)
Stars: ✭ 41 (+70.83%)
Mutual labels:  django-rest-framework

drf-psq

Build Status Codacy Badge codecov License: MIT PyPI version shields.io

drf-psq is an extension for the Django REST framework that gives support for having action-based permission_classes, serializer_class, and queryset dependent on permission-based rules. In a typical DRF project, you probably have faced the problem of setting different permissions for different actions with different serializers, all of which are dependent on some rules and you have to write too many if statements and override some methods to achieve this goal! Well, you don't have to do those kinds of hard stuff anymore. drf-psq is made to solve this problem and even more!

Setup

DRF Version

There is a bug in combining permission classes in DRF version 3.11.* or lower. It is recommended to use DRF version 3.12.0 or higher. Check this link out for more information.

Install Package

pip install drf-psq

Add to Project

INSTALLED_APPS = [
    ...
    'rest_framework',
    'drf_psq',
    ...
]

Simple Usage

Consider this scenario:

  • There is a user class.
  • Admins can create new users or view a list of users with complete information on each user.
  • Admins can view or edit users' complete information.
  • Admins can delete a user.
  • Each user can view or edit their basic information.

In a typical DRF project, you would probably implement the code below for this scenario:

class IsSelf(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        return request.user == obj


class UserViewSet(viewsets.ModelViewSet):

    queryset = User.objects.all()
    serializer_class = UserFullSerializer
    permission_classes = [IsAdminUser]


    def get_serializer_class(self):
        cond1 = bool(self.action in ['retrieve', 'update', 'partial_update'])

        obj = self.get_object()
        cond2 = bool(
            IsAuthenticated.has_permission(self.request, None) and
            IsSelf.has_object_permission(self.request, None, obj)
        )

        if cond1 and cond2:
            return UserBasicSerializer

        return super().get_serializer_class()


    def get_permission_classes(self):
        if self.action in ['retrieve', 'update', 'partial_update']:
            return [(IsAuthenticated & IsSelf) | IsAdminUser]

        return super().get_permission_classes()

This implementation has too many unrelated codes to views' logic and also in more complex scenarios, there would be a higher chance of making mistakes and causing bugs. But, there is no need to implement this significant amount of hard code in every viewset of each project. Using drf-psq all these codes can be replaced by only a configuration dictionary.

Now let drf-psq do the work:

from drf_psq import PsqMixin, Rule, psq

class UserViewSet(PsqMixin, viewsets.ModelViewSet):

    queryset = User.objects.all()
    serializer_class = UserFullSerializer
    permission_classes = [IsAdminUser]

    psq_rules = {
        ('retrieve', 'update', 'partial_update'): [
            Rule([IsAdminUser], UserFullSerializer),
            Rule([IsAuthenticated & IsSelf], UserBasicSerializer)
        ]
    }

Well, it seems much more simple!

Now let's take a look at each component of this extension and put them all together in a complete example.

Components

1. PsqMixin class

A mixin that contains logics of drf-psq and viewsets must inherit it in order to enable psq functionalities. Make sure that this mixin is the first one on the list of inherited classes. All the access rules should be defined in the psq_rules dictionary.

2. psq_rules dictionary

A dictionary that contains access rules. Keys can be strings or tuples of strings, indicating the name of the action(s). Values are a list of Rule classes. Each Rule class contains information about an access rule. More details are provided in the later subsections.

  • Important note: The roles in the mentioned list will be checked respectively and their order matters. The first matched rule will be selected to be applied. If none of them matches, the action will not be performed.

3. psq decorator

A decorator that can be used instead of psq_rules dictionary in case you are a fan of decorators in python! Also, it is possible to combine both psq decorators and psq_rules and use them at the same time.

Example:

class UserViewSet(PsqMixin, viewsets.ModelViewSet):

    queryset = User.objects.all()
    serializer_class = UserBasicSerializer
    permission_classes = [IsAuthenticated]

    @psq([
        Rule([IsAdminUser], UserFullSerializer),
        Rule([IsAuthenticated], UserBasicSerializer)
    ])
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

4. Rule class

A class that holds any information required for an access rule. Its attributes are explained below. It is to be noted that the default values declared in the viewset class (or settings) will be considered as the default value for all these attributes.

4.1. permission_classes

A list of permission classes that specifies who has access to this rule.

4.2. serializer_class

A serializer class which will be used for serializing objects related to this access rule.

4.3. queryset

A lambda function that enables you to use different querysets for each access rule. This lambda function takes self (the viewset class) as its argument.

Here is an example that allows admin users to get a list of all users with their complete information, while other authenticated users can only get a list of non-admin users with their basic information:

class UserViewSet(PsqMixin, viewsets.ModelViewSet):

    queryset = User.objects.all()
    serializer_class = UserFullSerializer
    permission_classes = [IsAdminUser]

    psq_rules = {
        'list': [
            Rule([IsAdminUser]),
            Rule(
                [IsAuthenticated],
                UserBasicSerializer,
                lambda self: User.objects.filter(is_superuser=False, is_staff=False)
            )
        ]
    }

4.4. get_obj

In my opinion, this is the coolest feature in this extension!

A lambda function that enables you to support object-level permissions in associated models to the main model. It takes self (the viewset class), and obj (the object returns by get_object method) as its arguments.

For example, consider this scenario: we have some libraries that each one contains some books. Each user can register in just one library (for simplicity's sake). Now you want to implement a mechanism to allow users to only access the books in the library they have registered at. Let's see how its code will be by using drf-psq.

################################### Models ###################################

from django.db import models


class Library(models.Model):
    name = models.CharField(max_length=50)

class Book(models.Model):
    library = models.ForeignKey(Library, on_delete=models.SET_NULL, null=True)

class User(models.Model):
    registered_library = models.ForeignKey(Library, on_delete=models.SET_NULL, null=True)


################################ Permissions #################################

from rest_framework import permissions


class IsRegisteredInLibrary(permissions.IsAuthenticated):

    def has_object_permission(self, request, view, obj):  # 'obj' is of type Library
        return request.user.registered_library == obj


################################### Views ####################################

from rest_framework import viewsets
from rest_framework import mixins

from drf_psq import PsqMixin, Rule, psq


class LibraryViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):

    queryset = Library.objects.all()
    serializer_class = LibrarySerializer
    permission_classes = [IsRegisteredInLibrary]


class BookViewSet(PsqMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):

    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [IsRegisteredInLibrary]

    psq_rules = {
        'retrieve': [Rule(get_obj=lambda self, obj: obj.library)]
    }

So, you can avoid creating too many redundant permission classes for each associated model.

A Complete Example

The complete source code of our library example can be found here.

Acknowledgments

  • Thanks to @jooof for his brilliant opinions and suggestions.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].