Skip to main content

DRF gears ⚙ ⚙ ⚙

Some gears collection for getting life a little bit better.

Installation

pip install drf-gears

How to use

ConditionalQuerysetMixin

It gives you an ability to have multiple get_queryset methods for every needs.

Querysets mapping

querysets - is a dictionary of named querysets. You can provide different querysets for any action.

from rest_framework import viewsets
from rest_framework.decorators import action
from gears import ConditionalQuerysetMixin

from .models import MyModel


class MyModelViewSet(
    ConditionalQuerysetMixin,
    viewsets.ModelViewSet,
):
    querysets = {
        'list': MyModel.objects.all(),
        'custom': MyModel.objects.filter(),
    }

    @action(methods=['get'])
    def some_action(self, request, *args, **kwargs):
        qs = self.get_queryset(name='custom')
        ...

Named queryset method

Use named methods like get_name_queryset, where name is an any name.

from rest_framework import viewsets
from rest_framework.decorators import action
from gears import ConditionalQuerysetMixin

from .models import MyModel

class MyModelViewSet(
    ConditionalQuerysetMixin,
    viewsets.ModelViewSet,
):
    def get_custom_queryset(self, qs):
        # do something
        return qs

    @action(methods=['get'])
    def some_action(self, request, *args, **kwargs):
        qs = self.get_queryset(name='custom')
        ...

ViewSet actions

If ConditionalQuerysetMixin can't find a mapped or named queryset, it will try to find a method with a ViewSet action instead of name.

E.g: get_list_queryset, get_update_queryset, get_custom_action_queryset.

from rest_framework import viewsets
from gears import ConditionalQuerysetMixin

from .models import MyModel

class MyModelViewSet(
    ConditionalQuerysetMixin,
    viewsets.ModelViewSet,
):
    def get_list_queryset(self, qs):
        # do something
        return qs
    
    def get_retrieve_queryset(self, qs):
        # do something
        return qs
    
    def get_some_action_queryset(self, qs):
        # do something
        return qs

SerializersMixin

Use this mixin if you need different serializers for any action. By default, will be used a serializer_class or a default item of serializers dictionary.

By the way, you can change the default key providing default_name attribute.

from rest_framework import viewsets
from rest_cogs.mixins import SerializersMixin

class SomeViewSet(
    SerializersMixin,
    viewsets.ModelViewSet,
):
    serializers = {
        'default': DefaultModelSerializer,
        'list': AdditionalModelSerializer,
    }

Also, you can use a get_serializer method providing a specific name when you need to use a particular serializer.

from rest_framework import viewsets
from rest_framework.decorators import action
from rest_cogs.mixins import SerializersMixin

class SomeViewSet(
    SerializersMixin,
    viewsets.ModelViewSet,
):
    serializers = {
        'default': DefaultModelSerializer,
        'some_action': SomeActionModelSerializer,
        'list': ListModelSerializer,
    }

    @action(methods=['get'])
    def some_action(self, request, *args, **kwargs):
        serializer = self.get_serializer()  # SomeActionModelSerializer
        serializer = self.get_serializer(serializer_name='list')  # ListModelSerializer
        ...

PermissionsMixin

This part of specification is in progress.

Renderers

There are a pair of things, which makes a charm when you work with API responses.

Imagine, every API response has an expected structure. I mean every response!

For example, take a look at this paginated objects list API response:

{
  "success": true,
  "status_code": 200,
  "pagination": {
    "count": 2,
    "next": null,
    "previous": null
  },
  "errors": [],
  "data": [
    {
      "id": "c65e9bf7-1724-4593-bf57-394cea491887",
      "phone_number": "10001001001"
    },
    {
      "id": "d7c9342d-0bd8-44e7-8e3c-44a18ecbfb8f",
      "phone_number": "10001001002"
    }
  ]
}

... and this validation error response.

{
  "success": false,
  "status_code": 400,
  "pagination": null,
  "errors": [
    [
      {
        "code": "value_required",
        "location": "phone_number",
        "description": "This field is required.",
        "detail": null
      }
    ],
    [
      {
        "code": "value_required",
        "location": "password",
        "description": "This field is required.",
        "detail": null
      }
    ]
  ],
  "data": null
}

... and this authorization error too.

{
  "success": false,
  "status_code": 403,
  "pagination": null,
  "errors": [
    {
      "code": "not_authenticated",
      "location": null,
      "description": "Authentication credentials were not provided.",
      "detail": null
    }
  ],
  "data": null
}

API renderer

All of these and any possible response will have the same structure: boolean success, integer status_code, pagination object, a list of errors objects and the requested data of course.

You should just use a single gear for it:

REST_FRAMEWORK = {
    ...
    'DEFAULT_RENDERER_CLASSES': (
        'gears.ApiRenderer',  # <-- that's it
    ),
    ...
}

Exception handler

Also, you could use the exception handler, if you want to see the same structure errors:

REST_FRAMEWORK = {
    ...
    'EXCEPTION_HANDLER': 'gears.exception_handler',  # <-- that's it
    ...
}

Error codes mapping

You have an ability to remap the standard error codes if you want. Yeah, sometimes the standard ones are not verbose enough. But you could handle this codes and remap them on your taste. Just add the following settings variable and describe the errors' behaviour. FYI, you're able to set not only the codes, but description, details and location are available as well.

# setting.py

from gears import Error

RESPONSE_ERROR_MAPPING = {
    'required': Error(code='value_required'),
    ...
}

JWT helpers

Before we start. We assumed you use Django Rest Framework and djangorestframework-simplejwt packages for handling the JWT authorisation process.

There are some gears which want to help you fill tokens with an additional payload.

Firstly, add the JWTUserModelMixin to your user model. It delivers a set of methods for extending the tokens.

from gears import JWTUserModelMixin

class User(JWTUserModelMixin, BaseUserModel):
    ...
    def get_public_jwt_data(self):
        return {
            'name': self.name,
        }

    def get_private_jwt_data(self):
        return {
            'address': self.address,
        }

You should override these two method. The first one must return a dictionary with an open data. The second one must return a dictionary with a sensitive data, which must be closed for anyone.

Keep on mind, JWT tokens could be unpacked by any person, so the information inside the token available for anyone who has this token. If you need to fill JWT token with a sensitive data, please put it to the get_private_jwt_data. The entire data in this method will be encrypted with a secret symmetric key you will generate on the next step.

Now we should generate a secret key and put it into the settings:

from gears import TokenEncryption

secret_key = TokenEncryption.generate_key()

The value of the secret_key you must put to the project's settings. It would be better to keep it in the virtual environment variable.

import os

JWT_PAYLOAD_ENCRYPTION_KEY = os.environ.get('JWT_PAYLOAD_ENCRYPTION_KEY')

Details

There was used the symmetric Fernet algorithm. Anyone, who has a secret key could decrypt data.

An example how to do it on python:

from gears import TokenEncryption

decrypted_data = TokenEncryption.decrypt_data(encrypted_data, secret_key)

Google help you if you need similar functionality for another programming language.

Metadata

Release files for rest-gears 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rest-gears 0.1.1
File Size Uploaded
rest-gears-0.1.1.tar.gz 11.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rest-gears 0.1.1
File Interpreter ABI Platform
rest_gears-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 21.8 kB

Release files / rest-gears-0.1.1.tar.gz

Download URL rest-gears-0.1.1.tar.gz
Size 11.1 kB
Tags Source
SHA-256 checksum
How to use checksums
c5768b5f24a37a86a8721ddc2b62eb6ff916d4475597e7c0c1957b4bbf2668d8
BLAKE2b-256 checksum
How to use checksums
d83091ac90a26429c8b1aa616a858415be3c29af0dc8b3f32a68b54b307dd684
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.9.6

Release files / rest_gears-0.1.1-py3-none-any.whl

Download URL rest_gears-0.1.1-py3-none-any.whl
Size 10.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7fe46c8e9e92455e41f823da749df7298ad71b7a0b26fb979b2ad062667192f4
BLAKE2b-256 checksum
How to use checksums
fb9c19d36f9519fec966b470d13718cac3ef230503823bfd9a5d07c01a84e837
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.9.6

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page