Skip to main content

Declarative Django request validation for RESTful APIs

Project description

Django API Forms

PyPI version codecov

Django Forms approach in the processing of a RESTful HTTP request payload (especially for content type like JSON or MessagePack) without HTML front-end.

Motivation

The main idea was to create a simple and declarative way to specify the format of expecting requests with the ability to validate them. Firstly, I tried to use Django Forms to validate my API requests (I use pure Django in my APIs). I have encountered a problem with nesting my requests without a huge boilerplate. Also, the whole HTML thing was pretty useless in my RESTful APIs.

I wanted to:

  • define my requests as object (Form),
  • pass the request to my defined object (form = Form.create_from_request(request)),
  • validate my request form.is_valid(),
  • extract data form.clean_data property.

I wanted to keep:

So I have decided to create a simple Python package to cover all my expectations.

Installation

# Using pip
pip install django-api-forms

# Using poetry
poetry add django-api-forms

# Local installation
python -m pip install .

Optional:

# msgpack support (for requests with Content-Type: application/x-msgpack)
poetry add msgpack

# ImageField support
poetry add Pillow

Install application in your Django project by adding django_api_forms to yours INSTALLED_APPS:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django_api_forms'
)

You can change the default behavior of population strategies or parsers using these settings (listed with default values). Keep in mind, that dictionaries are not replaced by your settings they are merged with defaults.

For more information about the parsers and the population strategies check the documentation.

DJANGO_API_FORMS_POPULATION_STRATEGIES = {
    'django_api_forms.fields.FormFieldList': 'django_api_forms.population_strategies.IgnoreStrategy',
    'django_api_forms.fields.FileField': 'django_api_forms.population_strategies.IgnoreStrategy',
    'django_api_forms.fields.ImageField': 'django_api_forms.population_strategies.IgnoreStrategy',
    'django_api_forms.fields.FormField': 'django_api_forms.population_strategies.IgnoreStrategy',
    'django.forms.models.ModelMultipleChoiceField': 'django_api_forms.population_strategies.IgnoreStrategy',
    'django.forms.models.ModelChoiceField': 'django_api_forms.population_strategies.ModelChoiceFieldStrategy'
}

DJANGO_API_FORMS_DEFAULT_POPULATION_STRATEGY = 'django_api_forms.population_strategies.BaseStrategy'

DJANGO_API_FORMS_PARSERS = {
    'application/json': 'json.loads',
    'application/x-msgpack': 'msgpack.loads'
}

Example

Simple nested JSON request

{
  "title": "Unknown Pleasures",
  "type": "vinyl",
  "artist": {
    "_name": "Joy Division",
    "genres": [
      "rock",
      "punk"
    ],
    "members": 4
  },
  "year": 1979,
  "songs": [
    {
      "title": "Disorder",
      "duration": "3:29"
    },
    {
      "title": "Day of the Lords",
      "duration": "4:48",
      "metadata": {
        "_section": {
          "type": "ID3v2",
          "offset": 0,
          "byteLength": 2048
        },
        "header": {
          "majorVersion": 3,
          "minorRevision": 0,
          "size": 2038
        }
      }
    }
  ],
  "metadata": {
    "created_at": "2019-10-21T18:57:03+0100",
    "updated_at": "2019-10-21T18:57:03+0100"
  }
}

Django API Forms equivalent + validation

from enum import Enum

from django.core.exceptions import ValidationError
from django.forms import fields

from django_api_forms import FieldList, FormField, FormFieldList, DictionaryField, EnumField, AnyField, Form


class AlbumType(Enum):
    CD = 'cd'
    VINYL = 'vinyl'


class ArtistForm(Form):
    class Meta:
        mapping = {
            '_name': 'name'
        }

    name = fields.CharField(required=True, max_length=100)
    genres = FieldList(field=fields.CharField(max_length=30))
    members = fields.IntegerField()


class SongForm(Form):
    title = fields.CharField(required=True, max_length=100)
    duration = fields.DurationField(required=False)
    metadata = AnyField(required=False)


class AlbumForm(Form):
    title = fields.CharField(max_length=100)
    year = fields.IntegerField()
    artist = FormField(form=ArtistForm)
    songs = FormFieldList(form=SongForm)
    type = EnumField(enum=AlbumType, required=True)
    metadata = DictionaryField(value_field=fields.DateTimeField())

    def clean_year(self):
        if self.cleaned_data['year'] == 1992:
            raise ValidationError("Year 1992 is forbidden!", 'forbidden-value')
        return self.cleaned_data['year']

    def clean(self):
        if (self.cleaned_data['year'] == 1998) and (self.cleaned_data['artist']['name'] == "Nirvana"):
            raise ValidationError("Sounds like a bullshit", code='time-traveling')
        if not self._request.user.is_authenticated():
            raise ValidationError("You can use request in form validation!")
        return self.cleaned_data



"""
Django view example
"""
def create_album(request):
    form = AlbumForm.create_from_request(request)
    if not form.is_valid():
        # Process your validation error
        print(form.errors)

    # Cleaned valid payload
    payload = form.cleaned_data
    print(payload)

If you want example with whole Django project, check out repository created by pawl django_api_forms_modelchoicefield_example, where he uses library with ModelChoiceField.

Running Tests

# install all dependencies
poetry install

# run code-style check
poetry run flake8 .

# run the tests
poetry run python runtests.py

Sponsorship

Navicat Premium

Navicat Premium is a super awesome database development tool for cool kids in the neighborhood that allows you to simultaneously connect to MySQL, MariaDB, MongoDB, SQL Server, Oracle, PostgreSQL, and SQLite databases from a single application. Compatible with cloud databases like Amazon RDS, Amazon Aurora, Amazon Redshift, Microsoft Azure, Oracle Cloud, Google Cloud and MongoDB Atlas. You can quickly and easily build, manage and maintain your databases.

Especially, I have to recommend their database design tool. Many thanks Navicat for supporting Open Source projects 🌈.


Made with ❤️ and ☕️ by Jakub Dubec, BACKBONE s.r.o. & contributors.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_api_forms-1.0.0rc7.tar.gz (15.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_api_forms-1.0.0rc7-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file django_api_forms-1.0.0rc7.tar.gz.

File metadata

  • Download URL: django_api_forms-1.0.0rc7.tar.gz
  • Upload date:
  • Size: 15.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.2.2 CPython/3.10.8 Darwin/22.1.0

File hashes

Hashes for django_api_forms-1.0.0rc7.tar.gz
Algorithm Hash digest
SHA256 2939ea81121d05eb3fb4011074bfffa5c5244717d4038d181a3a57d3e1b1130d
MD5 604c96da3887d13bc0a1972807fbce96
BLAKE2b-256 2681e5e3ddda9bbee2243bdb4c1f37914cc06beeafba3a44b44205c327c087d1

See more details on using hashes here.

File details

Details for the file django_api_forms-1.0.0rc7-py3-none-any.whl.

File metadata

File hashes

Hashes for django_api_forms-1.0.0rc7-py3-none-any.whl
Algorithm Hash digest
SHA256 29c44864e9412a8364fdf62849178aa1d13d3d8f9098197a06a3675bdac0d158
MD5 3154d53d6cb0277014845bbad048b5b2
BLAKE2b-256 3ab23a82cfc8cddeb3f4c17936a5169c80b1d7e01095c22cd2bc591def96b6e8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page