django-api-forms


Namedjango-api-forms JSON
Version 0.21.0 PyPI version JSON
download
home_pagehttps://github.com/Sibyx/django_api_forms
SummaryDeclarative Django request validation for RESTful APIs
upload_time2022-02-03 14:04:23
maintainer
docs_urlNone
authorJakub Dubec
requires_python
licenseMIT
keywords django forms request validation rest json
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Django API Forms

[![PyPI version](https://badge.fury.io/py/django-api-forms.svg)](https://badge.fury.io/py/django-api-forms)
[![codecov](https://codecov.io/gh/Sibyx/django_api_forms/branch/master/graph/badge.svg)](https://codecov.io/gh/Sibyx/django_api_forms)

[Django Forms](https://docs.djangoproject.com/en/3.2/topics/forms/) approach in the processing of a RESTful HTTP
request payload (especially for content type like [JSON](https://www.json.org/) or [MessagePack](https://msgpack.org/))
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](https://docs.djangoproject.com/en/3.0/topics/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:

- friendly declarative Django syntax,
([DeclarativeFieldsMetaclass](https://github.com/django/django/blob/master/django/forms/forms.py#L25) is beautiful),
- [Validators](https://docs.djangoproject.com/en/3.2/ref/validators/),
- [ValidationError](https://docs.djangoproject.com/en/3.2/ref/exceptions/#validationerror),
- [Form fields](https://docs.djangoproject.com/en/3.2/ref/forms/fields/) (In the end, I had to "replace" some of them).

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

## Installation

```shell script
# Using pip
pip install django-api-forms

# Using poetry
peotry add django-api-forms

# Using setup.py
python setup.py install
```

Optional:
```shell script
# 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`:

```python
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.

```python
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**

```json
{
  "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**

```python
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(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](https://github.com/pawl)
[django_api_forms_modelchoicefield_example](https://github.com/pawl/django_api_forms_modelchoicefield_example), where
he uses library with
[ModelChoiceField](https://docs.djangoproject.com/en/3.0/ref/forms/fields/#django.forms.ModelChoiceField).


## Running Tests

```shell script
# install all dependencies
poetry install

# run code-style check
poetry run flake8 .

# run the tests
poetry run python runtests.py
```

## Sponsorship

<img height="200" src="docs/navicat.png" align="left" alt="Navicat Premium">

[Navicat Premium](https://www.navicat.com/en/products/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](https://www.navicat.com/en/) for
supporting Open Source projects 🌈.

---
Made with ❤️ and ☕️ by Jakub Dubec, [BACKBONE s.r.o.](https://www.backbone.sk/en/) &
[contributors](https://github.com/Sibyx/django_api_forms/graphs/contributors).

# Changelog

## 0.21.0 : 15.12.2021

- **Feature**: Introduced `mapping`
- **Feature**: Override strategies using `field_type_strategy` and `field_strategy`

## 0.20.1 : 13.1.2022

- **Fix**: `DictionaryField` was unable to raise validation errors for keys

## 0.20.0 : 14.10.2021

Anniversary release 🥳

- **Feature**: Population strategies introduced
- **Feature**: `fill` method is deprecated and replaced by `populate`
- **Feature**: `Settings` object introduced (`form.settings`)
- **Feature**: Pluggable content-type parsers using `DJANGO_API_FORMS_PARSERS` setting

## 0.19.1 : 17.09.2021

- **Typing**: `mime` argument in `FileField` is supposed to be a `tuple`

## 0.19.0 : 12.07.2021

- **Feature**: `FieldList` and `FormFieldList` now supports optional min/max constrains using `min_length`/`max_length`

## 0.18.0 : 16.04.2021

- **Feature**: `ModelForm` class introduced (experimental, initial support - not recommended for production)

## 0.17.0 : 24.02.2021

- **Feature**: `fill_method` introduced

## 0.16.4 : 20.12.2020

- **Fix**: Pillow image object have to be reopened after `Image.verify()` call in `ImageField::to_python`

## 0.16.3 : 13.11.2020

- **Fix**: `ApiFormException('No clean data provided! Try to call is_valid() first.')` was incorrectly raised if
request payload was empty during `Form::fill` method call
- **Change**: `clean_data` property is by default `None` instead of empty dictionary

## 0.16.2 : 06.11.2020

- **Fix**: Fixed issue with `clean_` methods returning values resolved as False (`False`, `None`, `''`)

## 0.16.1 : 29.10.2020

- **Fix**: Ignore `ModelMultipleChoiceField` in `Form::fill()`

## 0.16.0 : 14.09.2020

- **Change**: Correctly resolve key postfix if `ModelChoiceField` is used in `Form::fill()`
- **Change**: `DjangoApiFormsConfig` is created
- **Note**: One more step to get rid of `pytest` in project (we don't need it)

## 0.15.1 : 29.08.2020

- **Feature**: `FileField.content_type` introduced (contains mime)

## 0.15.0 : 23.08.2020

- **Feature**: `FileField` and `ImageField` introduced
- **Note**: Defined extras in `setup.py` for optional `Pillow` and `msgpack` dependencies
- **Feature**: Working `Form::fill()` method for primitive data types. Introduced `IgnoreFillMixin`

## 0.14.0 : 07.08.2020

- **Feature**: `BaseForm._request` property introduced (now it's possible to use request in `clean_` methods)

## 0.13.0 : 09.07.2020

- **Fix**: Fixed `Content-Type` handling if `charset` or `boundary` is 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](https://python-poetry.org/) instead of [pipenv](https://github.com/pypa/pipenv)
- **Change**: Library renamed from `django_api_forms` to `django-api-forms` (cosmetic change without effect)

## 0.7.0 : 03.03.2020

- **Change**: Library renamed from `django_request_formatter` to `django_api_forms`
- **Change**: Imports in main module `django_api_forms`

## 0.6.0 : 18.02.2020

- **Feature**: `BooleanField` introduced

## 0.5.8 : 07.01.2020

- **Fix**: Pass `Invalid value` as `ValidationError` not as a `string`

## 0.5.7 : 07.01.2020

- **Fix**: Introduced generic `Invalid value` error message, if there is `AttributeError`, `TypeError`, `ValueError`

## 0.5.6 : 01.01.2020

- **Fix**: Fixing issue from version `0.5.5` but this time for real
- **Change**: Renamed version file from `__version__.py` to `version.py`

## 0.5.5 : 01.01.2020

- **Fix**: Check instance only if there is a value in `FieldList` and `FormFieldList`

## 0.5.4 : 24.12.2019

- **Fix**: Added missing `msgpack`` dependency to `setup.py`

## 0.5.3 : 20.12.2019

- **Feature**: Introduced generic `AnyField`

## 0.5.2 : 19.12.2019

- **Fix**: Skip processing of the `FormField` if value is not required and empty

## 0.5.1 : 19.12.2019

- **Fix**: Process `EnumField` even if it's not marked as required

## 0.5.0 : 16.12.2019

- **Change**: Use native `django.form.fields` if possible
- **Change**: Removed `kwargs` propagation from release `0.3.0`
- **Change**: Changed syntax back to `django.forms` compatible (e.g. `form.validate_{key}()` -> `form.clean_{key}()`)
- **Change**: `FieldList` raises `ValidationError` instead of `RuntimeException` if there is a type  in validation
- **Change**: Use private properties for internal data in field objects
- **Fixed**: `FieldList` returns values instead of `None`
- **Fix**: Fixed validation in `DictionaryField`
- **Maintenance**: Basic unit tests

## 0.4.3 : 29.11.2019

- **Fix**: Fixed `Form` has no attribute `self._data`

## 0.4.2 : 29.11.2019

- **Fix**: If payload is empty, create empty dictionary to avoid `NoneType` error

## 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 `kwargs` from `Form.is_valid()` to `Form.validate()` and `Form.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 by `Form.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 `DeclarativeFieldsMetaclass` because of custom `Field` class
- **Fix**: Do not return untouched fields in `Form::payload`
- **Fix**: Fix for None `default_validators` in `Field`

## 0.1.2 : 22:10.2019

- **Feature**: Support for `validation_{field}` methods in `Form` (initial support)

## 0.1.1 : 22.10.2019

- **Feature**: `EnumField`

## 0.1.0 : 22.10.2019

- **Feature**: First version of `Form` class
- **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`



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/Sibyx/django_api_forms",
    "name": "django-api-forms",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "django,forms,request,validation,rest,json",
    "author": "Jakub Dubec",
    "author_email": "jakub.dubec@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/90/58/acc4dd6d506b013217f255e6c7e2db1aad69067ffcf943f4ca1c44ea7a9c/django-api-forms-0.21.0.tar.gz",
    "platform": "",
    "description": "# Django API Forms\n\n[![PyPI version](https://badge.fury.io/py/django-api-forms.svg)](https://badge.fury.io/py/django-api-forms)\n[![codecov](https://codecov.io/gh/Sibyx/django_api_forms/branch/master/graph/badge.svg)](https://codecov.io/gh/Sibyx/django_api_forms)\n\n[Django Forms](https://docs.djangoproject.com/en/3.2/topics/forms/) approach in the processing of a RESTful HTTP\nrequest payload (especially for content type like [JSON](https://www.json.org/) or [MessagePack](https://msgpack.org/))\nwithout HTML front-end.\n\n## Motivation\n\nThe main idea was to create a simple and declarative way to specify the format of expecting requests with the ability\nto validate them. Firstly, I tried to use [Django Forms](https://docs.djangoproject.com/en/3.0/topics/forms/) to\nvalidate my API requests (I use pure Django in my APIs). I have encountered a problem with nesting my requests without\na huge boilerplate. Also, the whole HTML thing was pretty useless in my RESTful APIs.\n\nI wanted to:\n\n- define my requests as object (`Form`),\n- pass the request to my defined object (`form = Form.create_from_request(request)`),\n- validate my request `form.is_valid()`,\n- extract data `form.clean_data` property.\n\nI wanted to keep:\n\n- friendly declarative Django syntax,\n([DeclarativeFieldsMetaclass](https://github.com/django/django/blob/master/django/forms/forms.py#L25) is beautiful),\n- [Validators](https://docs.djangoproject.com/en/3.2/ref/validators/),\n- [ValidationError](https://docs.djangoproject.com/en/3.2/ref/exceptions/#validationerror),\n- [Form fields](https://docs.djangoproject.com/en/3.2/ref/forms/fields/) (In the end, I had to \"replace\" some of them).\n\nSo I have decided to create a simple Python package to cover all my expectations.\n\n## Installation\n\n```shell script\n# Using pip\npip install django-api-forms\n\n# Using poetry\npeotry add django-api-forms\n\n# Using setup.py\npython setup.py install\n```\n\nOptional:\n```shell script\n# msgpack support (for requests with Content-Type: application/x-msgpack)\npeotry add msgpack\n\n# ImageField support\npeotry add Pillow\n```\n\nInstall application in your Django project by adding `django_api_forms` to yours `INSTALLED_APPS`:\n\n```python\nINSTALLED_APPS = (\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django_api_forms'\n)\n```\n\nYou can change the default behavior of population strategies or parsers using these settings (listed with default\nvalues). Keep in mind, that dictionaries are not replaced by your settings they are merged with defaults.\n\nFor more information about the parsers and the population strategies check the documentation.\n\n```python\nDJANGO_API_FORMS_POPULATION_STRATEGIES = {\n    'django_api_forms.fields.FormFieldList': 'django_api_forms.population_strategies.IgnoreStrategy',\n    'django_api_forms.fields.FileField': 'django_api_forms.population_strategies.IgnoreStrategy',\n    'django_api_forms.fields.ImageField': 'django_api_forms.population_strategies.IgnoreStrategy',\n    'django_api_forms.fields.FormField': 'django_api_forms.population_strategies.IgnoreStrategy',\n    'django.forms.models.ModelMultipleChoiceField': 'django_api_forms.population_strategies.IgnoreStrategy',\n    'django.forms.models.ModelChoiceField': 'django_api_forms.population_strategies.ModelChoiceFieldStrategy'\n}\n\nDJANGO_API_FORMS_DEFAULT_POPULATION_STRATEGY = 'django_api_forms.population_strategies.BaseStrategy'\n\nDJANGO_API_FORMS_PARSERS = {\n    'application/json': 'json.loads',\n    'application/x-msgpack': 'msgpack.loads'\n}\n```\n\n## Example\n\n**Simple nested JSON request**\n\n```json\n{\n  \"title\": \"Unknown Pleasures\",\n  \"type\": \"vinyl\",\n  \"artist\": {\n    \"_name\": \"Joy Division\",\n    \"genres\": [\n      \"rock\",\n      \"punk\"\n    ],\n    \"members\": 4\n  },\n  \"year\": 1979,\n  \"songs\": [\n    {\n      \"title\": \"Disorder\",\n      \"duration\": \"3:29\"\n    },\n    {\n      \"title\": \"Day of the Lords\",\n      \"duration\": \"4:48\",\n      \"metadata\": {\n        \"_section\": {\n          \"type\": \"ID3v2\",\n          \"offset\": 0,\n          \"byteLength\": 2048\n        },\n        \"header\": {\n          \"majorVersion\": 3,\n          \"minorRevision\": 0,\n          \"size\": 2038\n        }\n      }\n    }\n  ],\n  \"metadata\": {\n    \"created_at\": \"2019-10-21T18:57:03+0100\",\n    \"updated_at\": \"2019-10-21T18:57:03+0100\"\n  }\n}\n```\n\n**Django API Forms equivalent + validation**\n\n```python\nfrom enum import Enum\n\nfrom django.core.exceptions import ValidationError\nfrom django.forms import fields\n\nfrom django_api_forms import FieldList, FormField, FormFieldList, DictionaryField, EnumField, AnyField, Form\n\n\nclass AlbumType(Enum):\n    CD = 'cd'\n    VINYL = 'vinyl'\n\n\nclass ArtistForm(Form):\n    class Meta:\n        mapping = {\n            '_name': 'name'\n        }\n\n    name = fields.CharField(required=True, max_length=100)\n    genres = FieldList(field=fields.CharField(max_length=30))\n    members = fields.IntegerField()\n\n\nclass SongForm(Form):\n    title = fields.CharField(required=True, max_length=100)\n    duration = fields.DurationField(required=False)\n    metadata = AnyField(required=False)\n\n\nclass AlbumForm(Form):\n    title = fields.CharField(max_length=100)\n    year = fields.IntegerField()\n    artist = FormField(form=ArtistForm)\n    songs = FormFieldList(form=SongForm)\n    type = EnumField(enum=AlbumType, required=True)\n    metadata = DictionaryField(fields.DateTimeField())\n\n    def clean_year(self):\n        if self.cleaned_data['year'] == 1992:\n            raise ValidationError(\"Year 1992 is forbidden!\", 'forbidden-value')\n        return self.cleaned_data['year']\n\n    def clean(self):\n        if (self.cleaned_data['year'] == 1998) and (self.cleaned_data['artist']['name'] == \"Nirvana\"):\n            raise ValidationError(\"Sounds like a bullshit\", code='time-traveling')\n        if not self._request.user.is_authenticated():\n            raise ValidationError(\"You can use request in form validation!\")\n        return self.cleaned_data\n\n\n\n\"\"\"\nDjango view example\n\"\"\"\ndef create_album(request):\n    form = AlbumForm.create_from_request(request)\n    if not form.is_valid():\n        # Process your validation error\n        print(form.errors)\n\n    # Cleaned valid payload\n    payload = form.cleaned_data\n    print(payload)\n```\n\nIf you want example with whole Django project, check out repository created by [pawl](https://github.com/pawl)\n[django_api_forms_modelchoicefield_example](https://github.com/pawl/django_api_forms_modelchoicefield_example), where\nhe uses library with\n[ModelChoiceField](https://docs.djangoproject.com/en/3.0/ref/forms/fields/#django.forms.ModelChoiceField).\n\n\n## Running Tests\n\n```shell script\n# install all dependencies\npoetry install\n\n# run code-style check\npoetry run flake8 .\n\n# run the tests\npoetry run python runtests.py\n```\n\n## Sponsorship\n\n<img height=\"200\" src=\"docs/navicat.png\" align=\"left\" alt=\"Navicat Premium\">\n\n[Navicat Premium](https://www.navicat.com/en/products/navicat-premium) is a super awesome database development tool for\ncool kids in the neighborhood that allows you to simultaneously connect to MySQL, MariaDB, MongoDB, SQL Server, Oracle,\nPostgreSQL, and SQLite databases from a single application. Compatible with cloud databases like Amazon RDS, Amazon\nAurora, Amazon Redshift, Microsoft Azure, Oracle Cloud, Google Cloud and MongoDB Atlas. You can quickly and easily\nbuild, manage and maintain your databases.\n\nEspecially, I have to recommend their database design tool. Many thanks [Navicat](https://www.navicat.com/en/) for\nsupporting Open Source projects \ud83c\udf08.\n\n---\nMade with \u2764\ufe0f and \u2615\ufe0f by Jakub Dubec, [BACKBONE s.r.o.](https://www.backbone.sk/en/) &\n[contributors](https://github.com/Sibyx/django_api_forms/graphs/contributors).\n\n# Changelog\n\n## 0.21.0 : 15.12.2021\n\n- **Feature**: Introduced `mapping`\n- **Feature**: Override strategies using `field_type_strategy` and `field_strategy`\n\n## 0.20.1 : 13.1.2022\n\n- **Fix**: `DictionaryField` was unable to raise validation errors for keys\n\n## 0.20.0 : 14.10.2021\n\nAnniversary release \ud83e\udd73\n\n- **Feature**: Population strategies introduced\n- **Feature**: `fill` method is deprecated and replaced by `populate`\n- **Feature**: `Settings` object introduced (`form.settings`)\n- **Feature**: Pluggable content-type parsers using `DJANGO_API_FORMS_PARSERS` setting\n\n## 0.19.1 : 17.09.2021\n\n- **Typing**: `mime` argument in `FileField` is supposed to be a `tuple`\n\n## 0.19.0 : 12.07.2021\n\n- **Feature**: `FieldList` and `FormFieldList` now supports optional min/max constrains using `min_length`/`max_length`\n\n## 0.18.0 : 16.04.2021\n\n- **Feature**: `ModelForm` class introduced (experimental, initial support - not recommended for production)\n\n## 0.17.0 : 24.02.2021\n\n- **Feature**: `fill_method` introduced\n\n## 0.16.4 : 20.12.2020\n\n- **Fix**: Pillow image object have to be reopened after `Image.verify()` call in `ImageField::to_python`\n\n## 0.16.3 : 13.11.2020\n\n- **Fix**: `ApiFormException('No clean data provided! Try to call is_valid() first.')` was incorrectly raised if\nrequest payload was empty during `Form::fill` method call\n- **Change**: `clean_data` property is by default `None` instead of empty dictionary\n\n## 0.16.2 : 06.11.2020\n\n- **Fix**: Fixed issue with `clean_` methods returning values resolved as False (`False`, `None`, `''`)\n\n## 0.16.1 : 29.10.2020\n\n- **Fix**: Ignore `ModelMultipleChoiceField` in `Form::fill()`\n\n## 0.16.0 : 14.09.2020\n\n- **Change**: Correctly resolve key postfix if `ModelChoiceField` is used in `Form::fill()`\n- **Change**: `DjangoApiFormsConfig` is created\n- **Note**: One more step to get rid of `pytest` in project (we don't need it)\n\n## 0.15.1 : 29.08.2020\n\n- **Feature**: `FileField.content_type` introduced (contains mime)\n\n## 0.15.0 : 23.08.2020\n\n- **Feature**: `FileField` and `ImageField` introduced\n- **Note**: Defined extras in `setup.py` for optional `Pillow` and `msgpack` dependencies\n- **Feature**: Working `Form::fill()` method for primitive data types. Introduced `IgnoreFillMixin`\n\n## 0.14.0 : 07.08.2020\n\n- **Feature**: `BaseForm._request` property introduced (now it's possible to use request in `clean_` methods)\n\n## 0.13.0 : 09.07.2020\n\n- **Fix**: Fixed `Content-Type` handling if `charset` or `boundary` is present\n\n## 0.12.0 : 11.06.2020\n\n- **Fix**: Do not call resolvers methods, if property is not required and not present in request\n\n## 0.11.0 : 10.06.2020\n\n- **Change**: Non specified non-required fields will no longer be available in the cleaned_data form attribute.\n\n## 0.10.0 : 01.06.2020\n\n- **Change**: All package exceptions inherits from `ApiFormException`.\n- **Fix**: Specifying encoding while opening files in `setup.py` (failing on Windows OS).\n\n## 0.9.0 : 11.05.2020\n\n- **Change**: Moved field error messages to default_error_messages for easier overriding and testing.\n- **Fix**: Fix KeyError when invalid values are sent to FieldList.\n- **Fix**: Removed unnecessary error checking in FieldList.\n\n## 0.8.0 : 05.05.2020\n\n- **Maintenance**: Add tests for fields\n- **Change**: Remove DeclarativeFieldsMetaclass and import from Django instead.\n- **Change**: Msgpack dependency is no longer required.\n- **Change**: Empty values passed into a FormField now return {} rather than None.\n- **Fix**: Throw a more user friendly error when passing non-Enums or invalid values to EnumField.\n\n## 0.7.1 : 13.04.2020\n\n- **Change** Use [poetry](https://python-poetry.org/) instead of [pipenv](https://github.com/pypa/pipenv)\n- **Change**: Library renamed from `django_api_forms` to `django-api-forms` (cosmetic change without effect)\n\n## 0.7.0 : 03.03.2020\n\n- **Change**: Library renamed from `django_request_formatter` to `django_api_forms`\n- **Change**: Imports in main module `django_api_forms`\n\n## 0.6.0 : 18.02.2020\n\n- **Feature**: `BooleanField` introduced\n\n## 0.5.8 : 07.01.2020\n\n- **Fix**: Pass `Invalid value` as `ValidationError` not as a `string`\n\n## 0.5.7 : 07.01.2020\n\n- **Fix**: Introduced generic `Invalid value` error message, if there is `AttributeError`, `TypeError`, `ValueError`\n\n## 0.5.6 : 01.01.2020\n\n- **Fix**: Fixing issue from version `0.5.5` but this time for real\n- **Change**: Renamed version file from `__version__.py` to `version.py`\n\n## 0.5.5 : 01.01.2020\n\n- **Fix**: Check instance only if there is a value in `FieldList` and `FormFieldList`\n\n## 0.5.4 : 24.12.2019\n\n- **Fix**: Added missing `msgpack`` dependency to `setup.py`\n\n## 0.5.3 : 20.12.2019\n\n- **Feature**: Introduced generic `AnyField`\n\n## 0.5.2 : 19.12.2019\n\n- **Fix**: Skip processing of the `FormField` if value is not required and empty\n\n## 0.5.1 : 19.12.2019\n\n- **Fix**: Process `EnumField` even if it's not marked as required\n\n## 0.5.0 : 16.12.2019\n\n- **Change**: Use native `django.form.fields` if possible\n- **Change**: Removed `kwargs` propagation from release `0.3.0`\n- **Change**: Changed syntax back to `django.forms` compatible (e.g. `form.validate_{key}()` -> `form.clean_{key}()`)\n- **Change**: `FieldList` raises `ValidationError` instead of `RuntimeException` if there is a type  in validation\n- **Change**: Use private properties for internal data in field objects\n- **Fixed**: `FieldList` returns values instead of `None`\n- **Fix**: Fixed validation in `DictionaryField`\n- **Maintenance**: Basic unit tests\n\n## 0.4.3 : 29.11.2019\n\n- **Fix**: Fixed `Form` has no attribute `self._data`\n\n## 0.4.2 : 29.11.2019\n\n- **Fix**: If payload is empty, create empty dictionary to avoid `NoneType` error\n\n## 0.4.1 : 14.11.2019\n\n- **Feature**: Introduced `UUIDField`\n\n## 0.4.0 : 13.11.2019\n\n- **Feature**: Introduced `DictionaryField`\n\n## 0.3.0 : 11.11.2019\n\n- **Feature**: Propagate `kwargs` from `Form.is_valid()` to `Form.validate()` and `Form.validate_{key}()` methods\n\n## 0.2.1 : 4.11.2019\n\n- **Fix**: Fixed `to_python()` in FormFieldList\n\n## 0.2.0 : 31.10.2019\n\n- **Change**: `Form.validate()` replaced by `Form.is_valid()`\n- **Feature**: `Form.validate()` is now used as a last step of form validation and it's aimed to be overwritten if\nneeded\n- **Note**: Unit tests initialization\n\n## 0.1.6 : 24.10.2019\n\n- **Fix**: Non-required EnumField is now working\n- **Feature**: WIP: Initial method for filling objects `Form::fill()`\n\n## 0.1.5 : 23.10.2019\n\n- **Fix**: Assign errors to form before raising `ValidationError`\n\n## 0.1.4 : 23.10.2019\n\n- **Fix**: Do not return empty error records in `Form:errors`\n\n## 0.1.3 : 23.10.2019\n\n- **Fix**: Use custom `DeclarativeFieldsMetaclass` because of custom `Field` class\n- **Fix**: Do not return untouched fields in `Form::payload`\n- **Fix**: Fix for None `default_validators` in `Field`\n\n## 0.1.2 : 22:10.2019\n\n- **Feature**: Support for `validation_{field}` methods in `Form` (initial support)\n\n## 0.1.1 : 22.10.2019\n\n- **Feature**: `EnumField`\n\n## 0.1.0 : 22.10.2019\n\n- **Feature**: First version of `Form` class\n- **Feature**: `CharField`\n- **Feature**: `IntegerField`\n- **Feature**: `FloatField`\n- **Feature**: `DecimalField`\n- **Feature**: `DateField`\n- **Feature**: `TimeField`\n- **Feature**: `DateTimeField`\n- **Feature**: `DurationField`\n- **Feature**: `RegexField`\n- **Feature**: `EmailField`\n- **Feature**: `BooleanField`\n- **Feature**: `RegexField`\n- **Feature**: `FieldList`\n- **Feature**: `FormField`\n- **Feature**: `FormFieldList`\n\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Declarative Django request validation for RESTful APIs",
    "version": "0.21.0",
    "project_urls": {
        "Homepage": "https://github.com/Sibyx/django_api_forms"
    },
    "split_keywords": [
        "django",
        "forms",
        "request",
        "validation",
        "rest",
        "json"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "52fadf2ac8f6bdb653d2019be7f912a290081f61389d32955adbd1a6d3d15c75",
                "md5": "98341ff8f9d6217322db3bdf76c244e0",
                "sha256": "ab29f1a9715565b550550687ecc20b8888a94964ca20d5caa469d2365f0c28d3"
            },
            "downloads": -1,
            "filename": "django_api_forms-0.21.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "98341ff8f9d6217322db3bdf76c244e0",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 15905,
            "upload_time": "2022-02-03T14:04:22",
            "upload_time_iso_8601": "2022-02-03T14:04:22.176334Z",
            "url": "https://files.pythonhosted.org/packages/52/fa/df2ac8f6bdb653d2019be7f912a290081f61389d32955adbd1a6d3d15c75/django_api_forms-0.21.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9058acc4dd6d506b013217f255e6c7e2db1aad69067ffcf943f4ca1c44ea7a9c",
                "md5": "589604bf556e0c1862fd03f7fd0cafaa",
                "sha256": "586263a4296baefd3ae5f4e04c875fe670398a8924a313d96bae9bbf54cd36f9"
            },
            "downloads": -1,
            "filename": "django-api-forms-0.21.0.tar.gz",
            "has_sig": false,
            "md5_digest": "589604bf556e0c1862fd03f7fd0cafaa",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 20002,
            "upload_time": "2022-02-03T14:04:23",
            "upload_time_iso_8601": "2022-02-03T14:04:23.711056Z",
            "url": "https://files.pythonhosted.org/packages/90/58/acc4dd6d506b013217f255e6c7e2db1aad69067ffcf943f4ca1c44ea7a9c/django-api-forms-0.21.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2022-02-03 14:04:23",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Sibyx",
    "github_project": "django_api_forms",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "django-api-forms"
}
        
Elapsed time: 0.29976s