Skip to content

Commit b20971f

Browse files
committed
code refactor
1 parent e9a3ddf commit b20971f

11 files changed

Lines changed: 650 additions & 2 deletions

File tree

.travis.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@ env:
77
- DJANGO="django==3.2.4"
88
matrix:
99
include:
10+
- name: "Python 3.11"
11+
python: "3.11"
12+
- name: "Python 3.10"
13+
python: "3.10"
14+
- name: "Python 3.9"
15+
python: "3.9"
1016
- name: "Python 3.8"
1117
python: "3.8"
1218
- name: "Python 3.7"
1319
python: "3.7"
1420
- name: "Python 3.6"
1521
python: "3.6"
16-
- name: "Django 1.11"
22+
- name: "Django 1.11"
1723
env:
1824
- DJANGO="django==1.11.27"
1925
- name: "Django 2.2"
@@ -24,6 +30,10 @@ matrix:
2430
env:
2531
- DJANGO="django==3.2.4"
2632
- DRF="djangorestframework==3.12.4"
33+
- name: "Django 4.2.6"
34+
env:
35+
- DJANGO="django==4.2.6"
36+
- DRF="djangorestframework==3.14.4"
2737

2838
install: pip install -r requirements.txt
2939

drf_nested_forms-1.1.8/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 emperorDuke
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

drf_nested_forms-1.1.8/MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include requirements.txt
2+
include README.md
3+
include LICENCE.txt

drf_nested_forms-1.1.8/README.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# DRF NESTED FORMS
2+
3+
A library that parses nested json or form data to python object.
4+
5+
[![Build Status](https://travis-ci.com/emperorDuke/nested_formdata.svg?branch=master)](https://travis-ci.com/emperorDuke/nested_formdata)
6+
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/emperorDuke/nested_formdata)](https://github.com/emperorDuke/nested_formdata/releases)
7+
[![PyPI - License](https://img.shields.io/pypi/l/drf_nested_forms)](https://pypi.python.org/pypi/drf-nested-forms)
8+
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/drf_nested_forms)](https://pypi.python.org/pypi/drf-nested-forms)
9+
[![PyPI - Django Version](https://img.shields.io/pypi/djversions/drf_nested_forms)](https://pypi.python.org/pypi/drf-nested-forms)
10+
[![PyPI](https://img.shields.io/pypi/v/drf_nested_forms)](https://pypi.python.org/pypi/drf-nested-forms)
11+
12+
13+
# Overview
14+
15+
SPA's, sometimes send nested form data or json as requests encoded by some javascript libraries like [json-form-data](https://github.com/hyperatom/json-form-data#readme) 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](https://github.com/beda-software/drf-writable-nested#readme) or used directly in the code.
16+
17+
# Installation
18+
19+
It is available via pypi:
20+
21+
```
22+
pip install drf_nested_forms
23+
```
24+
25+
# Usage
26+
27+
The utiliy class can be used directly in any part of the code.
28+
29+
```python
30+
31+
from drf_nested_forms.utils import NestedForm
32+
33+
data = {
34+
'item[attribute][0][user_type]': 'size',
35+
'item[attribute][1][user_type]': '',
36+
'item[verbose][]': '',
37+
'item[variant][vendor_metric]': '[]',
38+
'item[variant][metric_verbose_name]': 'Large',
39+
'item[foo][baaz]': 'null',
40+
}
41+
42+
options = {
43+
'allow_blank': True,
44+
'allow_empty': False
45+
}
46+
```
47+
48+
## Note
49+
50+
`.is_nested()` should be called before accessing the `.data`
51+
52+
```python
53+
form = NestedForm(data, **options)
54+
form.is_nested(raise_exception=True)
55+
```
56+
57+
The parsed result will look like below:
58+
59+
```python
60+
print(form.data)
61+
62+
data = {
63+
'item': {
64+
'attribute': [
65+
{'user_type': 'size'},
66+
{'user_type': ''}
67+
],
68+
'verbose': [''],
69+
'variant': {
70+
'vendor_metric': None,
71+
'metric_verbose_name': 'Large'
72+
},
73+
'foo': { 'baaz': None }
74+
}
75+
}
76+
```
77+
78+
# DRF Integration
79+
80+
The parser is used with a djangorestframework view classes.
81+
82+
## Parser classes supported:
83+
84+
- `NestedMultiPartParser`: is a default DRF multipart parser that suppport parsing nested form data.
85+
- `NestedJSONParser`: is a default DRF JSONParser that support parsing nested json request.
86+
87+
Add the parser to your django settings file
88+
89+
```python
90+
91+
#...
92+
93+
REST_FRAMEWORK = {
94+
DEFAULT_PARSER_CLASSES = [
95+
# nested parser are just default DRF parsers with extended
96+
# functionalities to support nested
97+
98+
'drf_nested_forms.parsers.NestedMultiPartParser',
99+
'drf_nested_forms.parsers.NestedJSONPartParser',
100+
'rest_framework.parsers.FormParser',
101+
102+
# so this settings will work in respective of a nested request
103+
# or not
104+
]
105+
}
106+
107+
#...
108+
109+
```
110+
111+
To change default settings of the parsers, add `OPTIONS` to `NESTED_FORM_PARSER` with the new settings to your django settings file
112+
113+
```python
114+
#..
115+
116+
NESTED_FORM_PARSER = {
117+
'OPTIONS': {
118+
'allow_empty': False,
119+
'allow_blank': True
120+
}
121+
}
122+
123+
#...
124+
125+
```
126+
127+
The parsers can also be used directly in a `rest_framework` view class
128+
129+
```python
130+
131+
from rest_framework.views import APIView
132+
from rest_framework.parsers import FormParser
133+
from rest_framework.response import Response
134+
135+
from drf_nested_forms.parsers import NestedMultiPartParser, NestedJSONParser
136+
137+
class TestMultiPartParserView(APIView):
138+
parser_classes = (NestedMultiPartParser, FormParser)
139+
140+
def post(self, request):
141+
return Response(data=request.data, status=200)
142+
143+
# or
144+
145+
class TestJSONParserView(APIView):
146+
parser_classes = (NestedJSONParser, FormParser)
147+
148+
def post(self, request):
149+
return Response(data=request.data, status=200)
150+
151+
```
152+
153+
For example, a form or JSON data with nested params like below can be posted to any of the above drf view:
154+
155+
```python
156+
data = {
157+
'[0][attribute]': 'true',
158+
'[0][verbose][0]': 'bazz',
159+
'[0][verbose][1]': 'foo',
160+
'[0][variant][vendor_metric]': 'null',
161+
'[0][variant][metric_verbose_name]': 'Large',
162+
'[0][foo][baaz]': 'false',
163+
'[1][attribute]': 'size',
164+
'[1][verbose]': '[]',
165+
'[1][variant][vendor_metric]': '{}',
166+
'[1][variant][metric_verbose_name][foo][baaz][]': 'Large',
167+
'[1][foo][baaz]': '',
168+
'[1][logo]': '235'
169+
}
170+
```
171+
172+
after being parsed, the `request.data` will look like this:
173+
174+
```python
175+
print(request.data)
176+
177+
data = [
178+
{
179+
'attribute': True,
180+
'verbose': ['bazz', 'foo'],
181+
'variant': {
182+
'vendor_metric': None,
183+
'metric_verbose_name': 'Large'
184+
},
185+
'foo': { 'baaz': False }
186+
},
187+
{
188+
'attribute': 'size',
189+
'verbose': None,
190+
'variant': {
191+
'vendor_metric': None,
192+
'metric_verbose_name': { 'foo': { 'baaz': ['Large'] } }
193+
},
194+
'foo': { 'baaz': '' },
195+
'logo': 235
196+
}
197+
]
198+
```
199+
200+
# Options
201+
202+
| Option | Default | Description |
203+
| ----------- | ------- | ------------------------------------- |
204+
| allow_blank | `True` | shows empty string `''` in the object |
205+
| allow_empty | `False` | shows empty `list` or `dict` object |
206+
207+
# Running Tests
208+
209+
To run the current test suite, execute the following from the root of the project:
210+
211+
```
212+
python runtests.py
213+
```
214+
215+
# Author
216+
217+
@Copyright 2021, Duke Effiom
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
class ParseError(Exception):
3+
"""
4+
Unable to parse data type
5+
"""
6+
pass
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
def is_dict(obj):
3+
"""
4+
Check if object is a dictionary
5+
"""
6+
return isinstance(obj, dict)
7+
8+
9+
def is_list(obj):
10+
"""
11+
Check if object is a list
12+
"""
13+
return isinstance(obj, list)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from rest_framework.parsers import MultiPartParser, JSONParser
2+
3+
from .utils import NestedForm
4+
from .settings import api_settings
5+
6+
7+
class NestedMultiPartParser(MultiPartParser):
8+
"""
9+
Parser for multipart form data that is nested and also
10+
it may include files
11+
"""
12+
options = api_settings.OPTIONS
13+
14+
def parse(self, stream, media_type=None, parser_context=None):
15+
parsed = super().parse(stream, media_type, parser_context)
16+
17+
# files and data have to be merged into one
18+
if parsed.files:
19+
self._full_data = parsed.data.copy()
20+
self._full_data.update(parsed.files)
21+
else:
22+
self._full_data = parsed.data
23+
24+
form = NestedForm(self._full_data, **self.options)
25+
26+
if form.is_nested():
27+
return form.data
28+
29+
return parsed
30+
31+
32+
class NestedJSONParser(JSONParser):
33+
"""
34+
Parser for JSON data that is nested
35+
"""
36+
options = api_settings.OPTIONS
37+
38+
def parse(self, stream, media_type=None, parser_context=None):
39+
parsed = super().parse(stream, media_type, parser_context)
40+
41+
form = NestedForm(parsed, **self.options)
42+
43+
if form.is_nested():
44+
return form.data
45+
46+
return parsed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from django.conf import settings
2+
from rest_framework.settings import APISettings
3+
4+
5+
USER_SETTINGS = getattr(settings, 'NESTED_FORM_PARSER', {})
6+
7+
DEFAULTS = {
8+
'OPTIONS': {
9+
'allow_empty': False,
10+
'allow_blank': True
11+
}
12+
}
13+
14+
api_settings = APISettings(USER_SETTINGS, DEFAULTS)

0 commit comments

Comments
 (0)