drf-nested-forms

A library that parses nested json or form data to python object


Keywords
drf, nested, form, html_formsdrf_nested_forms, restframework, json
License
MIT
Install
pip install drf-nested-forms==1.1.8

Documentation

DRF NESTED FORMS

A library that parses nested json or form data to python object.

Build Status GitHub release (latest by date) PyPI - License PyPI - Python Version PyPI - Django Version PyPI

Overview

SPA's, sometimes send nested form data or json as requests encoded by some javascript libraries like json-form-data which can be difficult to handle due to the key naming conventions. This library helps to eliminate that difficulty, by parsing that nested requests into a more predictable python object that can be used by libraries like drf_writable_nested or used directly in the code.

Installation

It is available via pypi:

pip install drf_nested_forms

Usage

The utiliy class can be used directly in any part of the code.

from drf_nested_forms.utils import NestedForm

data = {
    'item[attribute][0][user_type]': 'size',
    'item[attribute][1][user_type]': '',
    'item[verbose][]': '',
    'item[variant][vendor_metric]': '[]',
    'item[variant][metric_verbose_name]': 'Large',
    'item[foo][baaz]': 'null',
}

options = {
    'allow_blank': True,
    'allow_empty': False
}

Note

.is_nested() should be called before accessing the .data

form = NestedForm(data, **options)
form.is_nested(raise_exception=True)

The parsed result will look like below:

print(form.data)

data = {
    'item': {
        'attribute': [
            {'user_type': 'size'},
            {'user_type': ''}
        ],
        'verbose': [''],
        'variant': {
            'vendor_metric': None,
            'metric_verbose_name': 'Large'
        },
        'foo': { 'baaz': None }
    }
}

DRF Integration

The parser is used with a djangorestframework view classes.

Parser classes supported:

  • NestedMultiPartParser: is a default DRF multipart parser that suppport parsing nested form data.
  • NestedJSONParser: is a default DRF JSONParser that support parsing nested json request.

Add the parser to your django settings file

#...

REST_FRAMEWORK = {
    DEFAULT_PARSER_CLASSES = [
        # nested parser are just default DRF parsers with extended
        # functionalities to support nested

        'drf_nested_forms.parsers.NestedMultiPartParser',
        'drf_nested_forms.parsers.NestedJSONPartParser',
        'rest_framework.parsers.FormParser',

        # so this settings will work in respective of a nested request
        # or not
    ]
}

#...

To change default settings of the parsers, add OPTIONS to NESTED_FORM_PARSER with the new settings to your django settings file

#..

NESTED_FORM_PARSER = {
    'OPTIONS': {
        'allow_empty': False,
        'allow_blank': True
    }
}

#...

The parsers can also be used directly in a rest_framework view class

from rest_framework.views import APIView
from rest_framework.parsers import FormParser
from rest_framework.response import Response

from drf_nested_forms.parsers import NestedMultiPartParser, NestedJSONParser

class TestMultiPartParserView(APIView):
    parser_classes = (NestedMultiPartParser, FormParser)

    def post(self, request):
        return Response(data=request.data, status=200)

# or

class TestJSONParserView(APIView):
    parser_classes = (NestedJSONParser, FormParser)

    def post(self, request):
        return Response(data=request.data, status=200)

For example, a form or JSON data with nested params like below can be posted to any of the above drf view:

data = {
    '[0][attribute]': 'true',
    '[0][verbose][0]': 'bazz',
    '[0][verbose][1]': 'foo',
    '[0][variant][vendor_metric]': 'null',
    '[0][variant][metric_verbose_name]': 'Large',
    '[0][foo][baaz]': 'false',
    '[1][attribute]': 'size',
    '[1][verbose]': '[]',
    '[1][variant][vendor_metric]': '{}',
    '[1][variant][metric_verbose_name][foo][baaz][]': 'Large',
    '[1][foo][baaz]': '',
    '[1][logo]': '235'
}

after being parsed, the request.data will look like this:

print(request.data)

data = [
    {
        'attribute': True,
        'verbose': ['bazz', 'foo'],
        'variant': {
            'vendor_metric': None,
            'metric_verbose_name': 'Large'
        },
        'foo': { 'baaz': False }
    },
    {
        'attribute': 'size',
        'verbose': None,
        'variant': {
            'vendor_metric': None,
            'metric_verbose_name': { 'foo': { 'baaz': ['Large'] } }
        },
        'foo': { 'baaz': '' },
        'logo': 235
    }
]

Options

Option Default Description
allow_blank True shows empty string '' in the object
allow_empty False shows empty list or dict object

Running Tests

To run the current test suite, execute the following from the root of the project:

python runtests.py

Author

@Copyright 2021, Duke Effiom