Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

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

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.0rc8.tar.gz (13.5 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.0rc8-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: django_api_forms-1.0.0rc8.tar.gz
  • Upload date:
  • Size: 13.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.4.0 CPython/3.11.2 Darwin/22.3.0

File hashes

Hashes for django_api_forms-1.0.0rc8.tar.gz
Algorithm Hash digest
SHA256 ecf66502615e2192727712aac9ffe8abc4f367ff8f994c0e238f27f01efcee6b
MD5 e140ef549a35e7c9e81f48b94f3d3d48
BLAKE2b-256 141e0149bdffe609679be4ca3d086b6011c3d9f4b8b5e11901f967cb7ce0e7ea

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for django_api_forms-1.0.0rc8-py3-none-any.whl
Algorithm Hash digest
SHA256 cd6bdc258824c4d52d3c8849d4f8cfe21ceab15e998400afc24fb363605c0daa
MD5 ef5c2cc55be7dfd88848cb263993b368
BLAKE2b-256 b2d426c1efab181c464af23ee7965a013336a0218e2295bd82836ceccf14caeb

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.0.0rc8 This release

2 files

0.21.0

2 files

0.20.1

2 files

0.20.0

2 files

0.19.1

2 files

0.19.0

2 files

0.18.0

2 files

0.17.0

2 files

0.16.4

2 files

0.16.3

2 files

0.16.2

2 files

0.16.1

2 files

0.16.0

2 files

0.15.1

2 files

0.15.0

2 files

0.14.0

2 files

0.13.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.1

2 files

0.7.0

2 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