Django API Forms
Django Forms approach in paring and validation of request payload (especially for content type like JSON or MessagePack) without HTML front-end.
Motivation
Main idea was to create a simple and declarative way to specify format of expecting request with ability to validate them. Firstly I tried to use Django Forms to validate my API request (I use pure Django in my APIs). I have encountered a problem with nesting my requests without huge boilerplate. Also, the whole HTML thing was pretty useless in my RESTful APIs.
I wanted something 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_dataproperty
I wanted to keep:
- friendly declarative Django syntax (DeclarativeFieldsMetaclass is beautiful)
- Validators
- ValidationError
- Form fields (In the and, I had to "replace" some of them)
So I decided to create simple Python package to cover all my expectations.
Installation
# Using pip
pip install django-api-forms
# Using poetry
peotry add django-api-forms
# Using setup.py
python setup.py install
Optional:
# msgpack support (for requests with Content-Type: application/x-msgpack)
peotry add msgpack
# ImageField support
peotry 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'
)
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):
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(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 the tests
poetry run pytest
Made with ❤️ and ☕️ by Jakub Dubec & BACKBONE s.r.o.
Changelog
0.16.1 : 29.10.2020
- Fix: Ignore
ModelMultipleChoiceFieldinForm::fill()
0.16.0 : 14.09.2020
- Change: Correctly resolve key postfix if
ModelChoiceFieldis used inForm::fill() - Change:
DjangoApiFormsConfigis created - Note: One more step to get rid of
pytestin project (we don't need it)
0.15.1 : 29.08.2020
- Feature:
FileField.content_typeintroduced (contains mime)
0.15.0 : 23.08.2020
- Feature:
FileFieldandImageFieldintroduced - Note: Defined extras in
setup.pyfor optionalPillowandmsgpackdependencies - Feature: Working
Form::fill()method for primitive data types. IntroducedIgnoreFillMixin
0.14.0 : 07.08.2020
- Feature:
BaseForm._requestproperty introduced (now it's possible to use request inclean_methods)
0.13.0 : 09.07.2020
- Fix: Fixed
Content-Typehandling ifcharsetorboundaryis present
0.12.0 : 11.06.2020
- Fix: Do not call resolvers methods, if property is not required and not present in request
0.11.0 : 10.06.2020
- Change: Non specified non-required fields will no longer be available in the cleaned_data form attribute.
0.10.0 : 01.06.2020
- Change: All package exceptions inherits from
ApiFormException. - Fix: Specifying encoding while opening files in
setup.py(failing on Windows OS).
0.9.0 : 11.05.2020
- Change: Moved field error messages to default_error_messages for easier overriding and testing.
- Fix: Fix KeyError when invalid values are sent to FieldList.
- Fix: Removed unnecessary error checking in FieldList.
0.8.0 : 05.05.2020
- Maintenance: Add tests for fields
- Change: Remove DeclarativeFieldsMetaclass and import from Django instead.
- Change: Msgpack dependency is no longer required.
- Change: Empty values passed into a FormField now return {} rather than None.
- Fix: Throw a more user friendly error when passing non-Enums or invalid values to EnumField.
0.7.1 : 13.04.2020
- Change Use poetry instead of pipenv
- Change: Library renamed from
django_api_formstodjango-api-forms(cosmetic change without effect)
0.7.0 : 03.03.2020
- Change: Library renamed from
django_request_formattertodjango_api_forms - Change: Imports in main module
django_api_forms
0.6.0 : 18.02.2020
- Feature:
BooleanFieldintroduced
0.5.8 : 07.01.2020
- Fix: Pass
Invalid valueasValidationErrornot as astring
0.5.7 : 07.01.2020
- Fix: Introduced generic
Invalid valueerror message, if there isAttributeError,TypeError,ValueError
0.5.6 : 01.01.2020
- Fix: Fixing issue from version
0.5.5but this time for real - Change: Renamed version file from
__version__.pytoversion.py
0.5.5 : 01.01.2020
- Fix: Check instance only if there is a value in
FieldListandFormFieldList
0.5.4 : 24.12.2019
- Fix: Added missing
msgpack`` dependency tosetup.py`
0.5.3 : 20.12.2019
- Feature: Introduced generic
AnyField
0.5.2 : 19.12.2019
- Fix: Skip processing of the
FormFieldif value is not required and empty
0.5.1 : 19.12.2019
- Fix: Process
EnumFieldeven if it's not marked as required
0.5.0 : 16.12.2019
- Change: Use native
django.form.fieldsif possible - Change: Removed
kwargspropagation from release0.3.0 - Change: Changed syntax back to
django.formscompatible (e.g.form.validate_{key}()->form.clean_{key}()) - Change:
FieldListraisesValidationErrorinstead ofRuntimeExceptionif there is a type in validation - Change: Use private properties for internal data in field objects
- Fixed:
FieldListreturns values instead ofNone - Fix: Fixed validation in
DictionaryField - Maintenance: Basic unit tests
0.4.3 : 29.11.2019
- Fix: Fixed
Formhas no attributeself._data
0.4.2 : 29.11.2019
- Fix: If payload is empty, create empty dictionary to avoid
NoneTypeerror
0.4.1 : 14.11.2019
- Feature: Introduced
UUIDField
0.4.0 : 13.11.2019
- Feature: Introduced
DictionaryField
0.3.0 : 11.11.2019
- Feature: Propagate
kwargsfromForm.is_valid()toForm.validate()andForm.validate_{key}()methods
0.2.1 : 4.11.2019
- Fix: Fixed
to_python()in FormFieldList
0.2.0 : 31.10.2019
- Change:
Form.validate()replaced byForm.is_valid() - Feature:
Form.validate()is now used as a last step of form validation and it's aimed to be overwritten if needed - Note: Unit tests initialization
0.1.6 : 24.10.2019
- Fix: Non-required EnumField is now working
- Feature: WIP: Initial method for filling objects
Form::fill()
0.1.5 : 23.10.2019
- Fix: Assign errors to form before raising
ValidationError
0.1.4 : 23.10.2019
- Fix: Do not return empty error records in
Form:errors
0.1.3 : 23.10.2019
- Fix: Use custom
DeclarativeFieldsMetaclassbecause of customFieldclass - Fix: Do not return untouched fields in
Form::payload - Fix: Fix for None
default_validatorsinField
0.1.2 : 22:10.2019
- Feature: Support for
validation_{field}methods inForm(initial support)
0.1.1 : 22.10.2019
- Feature:
EnumField
0.1.0 : 22.10.2019
- Feature: First version of
Formclass - Feature:
CharField - Feature:
IntegerField - Feature:
FloatField - Feature:
DecimalField - Feature:
DateField - Feature:
TimeField - Feature:
DateTimeField - Feature:
DurationField - Feature:
RegexField - Feature:
EmailField - Feature:
BooleanField - Feature:
RegexField - Feature:
FieldList - Feature:
FormField - Feature:
FormFieldList
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django-api-forms-0.16.1.tar.gz.
File metadata
- Download URL: django-api-forms-0.16.1.tar.gz
- Upload date:
- Size: 16.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/3.2.0 pkginfo/1.6.1 requests/2.24.0 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.51.0 CPython/3.9.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f0a75d9de7bc8dc16e64a44ed7731e6b417b84e846b365f8aa3c9dcc735b5cc
|
|
| MD5 |
49f10f03b75203beb2fc5872f65f6c45
|
|
| BLAKE2b-256 |
f9caf54694e99ed42d307534f3673e7605e250cea50789c2f43bdc9c796ac265
|
File details
Details for the file django_api_forms-0.16.1-py3-none-any.whl.
File metadata
- Download URL: django_api_forms-0.16.1-py3-none-any.whl
- Upload date:
- Size: 12.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/3.2.0 pkginfo/1.6.1 requests/2.24.0 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.51.0 CPython/3.9.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b82533ae859da8f24456a3a4224b9d288cae81f12d372af9afa5f1fd0dee7ffd
|
|
| MD5 |
f448813029e5b5cdaa4cbe01fa231ee4
|
|
| BLAKE2b-256 |
01d5170fb75a44387d76d97d7c59dde48a9572b586aa577adf52ceea45e4fd55
|