Skip to content

Commit 64ae112

Browse files
committed
fix: drf parser and test
1 parent f1b7c61 commit 64ae112

File tree

4 files changed

+158
-81
lines changed

4 files changed

+158
-81
lines changed

README.md

Lines changed: 26 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![build](https://github.com/remigermain/nested-multipart-parser/actions/workflows/main.yml/badge.svg)](https://github.com/remigermain/nested-multipart-parser/actions/workflows/main.yml)
44
[![pypi](https://img.shields.io/pypi/v/nested-multipart-parser)](https://pypi.org/project/nested-multipart-parser/)
55

6-
Parser for nested data in multipart form, you can use it anyways, and you have a django rest framework integration
6+
Parser for nested data for '*multipart/form*', you can use it any python project and you have a django rest framework integration.
77

88
# Installation
99

@@ -13,13 +13,18 @@ pip install nested-multipart-parser
1313

1414
# How to use it
1515

16-
## for every framwork
16+
### Any python project
1717

1818
```python
1919
from nested_multipart_parser import NestedParser
2020

21+
options = {
22+
"separator": "bracket"
23+
}
24+
2125
def my_view():
22-
parser = NestedParser(data)
26+
# options is optional
27+
parser = NestedParser(data, options)
2328
if parser.is_valid():
2429
validate_data = parser.validate_data
2530
...
@@ -28,7 +33,7 @@ def my_view():
2833

2934
```
3035

31-
## for django rest framwork
36+
### Django rest framwork
3237

3338
```python
3439
from nested_multipart_parser.drf import DrfNestedParser
@@ -38,9 +43,10 @@ class YourViewSet(viewsets.ViewSet):
3843
parser_classes = (DrfNestedParser,)
3944
```
4045

46+
4147
## What is doing
4248

43-
the parser take the request data and transform to dictionary
49+
The parser take the request data and transform to dictionary
4450

4551
exemple:
4652

@@ -90,18 +96,18 @@ exemple:
9096

9197
## How is work
9298

93-
for this working perfectly you need to follow this rules:
99+
For this working perfectly you need to follow this rules:
94100

95-
- a first key need to be set ex: 'title[0]' or 'title', in both the first key is 'title'
96-
- each sub key need to seperate by brackets "[--your-key--]" or dot "." (depends of your options)
97-
- if sub key are a full number, is converted to list
98-
- if sub key is Not a number is converted to dictionary
101+
- a first key need to be set ex: `title[0]` or `title`, in both the first key is `title`
102+
- each sub key need to be seperate by brackets `[ ]` or dot `.` (depends of your options)
103+
- if sub key are a full number, is converted to list *ex:* `[0]` or `[42]`
104+
- if sub key is Not a number is converted to dictionary *ex:* `[username]` or `[article]`
99105
- by default,the duplicate keys can't be set (see options to override that)
100106
ex:
101107

102108
```python
103109
data = {
104-
'title[0]': 'my-value'
110+
'title[0]': 'my-value'``
105111
}
106112
# output
107113
output = {
@@ -162,7 +168,7 @@ for this working perfectly you need to follow this rules:
162168
data = {
163169
'the[0][chained][key][0][are][awesome][0][0]': 'im here !!'
164170
}
165-
# with "dot" separator in options is ;look like that
171+
# with "dot" separator in options is look like that
166172
data = {
167173
'the.0.chained.key.0.are.awesome.0.0': 'im here !!'
168174
}
@@ -190,39 +196,7 @@ for this working perfectly you need to follow this rules:
190196
}
191197
```
192198

193-
# How to use it
194-
195-
## for every framwork
196-
197-
```python
198-
from nested_multipart_parser import NestedParser
199-
200-
options = {
201-
"separator": "bracket"
202-
}
203-
204-
def my_view():
205-
# options is optional
206-
parser = NestedParser(data, options)
207-
if parser.is_valid():
208-
validate_data = parser.validate_data
209-
...
210-
else:
211-
print(parser.errors)
212-
213-
```
214-
215-
## for django rest framwork
216-
217-
```python
218-
from nested_multipart_parser.drf import DrfNestedParser
219-
...
220-
221-
class YourViewSet(viewsets.ViewSet):
222-
parser_classes = (DrfNestedParser,)
223-
```
224-
225-
## options
199+
## Options
226200

227201
```python
228202
{
@@ -231,13 +205,14 @@ class YourViewSet(viewsets.ViewSet):
231205
# with dot: article.title.authors.0: "jhon doe"
232206
'separator': 'bracket' or 'dot', # default is bracket
233207

208+
234209
# raise a expections when you have duplicate keys
235210
# ex :
236211
# {
237212
# "article": 42,
238213
# "article[title]": 42,
239214
# }
240-
'raise_duplicate': True,
215+
'raise_duplicate': True, # default is True
241216

242217
# overide the duplicate keys, you need to set "raise_duplicate" to False
243218
# ex :
@@ -252,16 +227,16 @@ class YourViewSet(viewsets.ViewSet):
252227
# "title": 42,
253228
# }
254229
# }
255-
'assign_duplicate': False
230+
'assign_duplicate': False # default is False
256231
}
257232
```
258233

259-
## options with django rest framwork
234+
## Options with django rest framwork
260235

261236
In your settings.py, add "DRF_NESTED_MULTIPART_PARSER"
262237

263238
```python
264-
#settings.py
239+
# settings.py
265240
...
266241

267242
DRF_NESTED_MULTIPART_PARSER = {
@@ -272,6 +247,6 @@ DRF_NESTED_MULTIPART_PARSER = {
272247
}
273248
```
274249

275-
## for frontend javscript
250+
## Javscript integration
276251

277-
You can use this [multipart-object](https://github.com/remigermain/multipart-object) library to easy convert object to flat nested object formated for this library
252+
You can use this [multipart-object](https://github.com/remigermain/multipart-object) library to easy convert object to flat nested object formated for this library

nested_multipart_parser/drf.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .parser import NestedParser as NestPars
2-
from rest_framework.parsers import MultiPartParser
3-
from django.http.multipartparser import MultiPartParserError
2+
from rest_framework.parsers import MultiPartParser, DataAndFiles
3+
from rest_framework.exceptions import ParseError
44
from django.http import QueryDict
55
from django.conf import settings
66

@@ -10,6 +10,13 @@ class NestedParser(NestPars):
1010
def __init__(self, data):
1111
super().__init__(data, getattr(settings, "DRF_NESTED_MULTIPART_PARSER", {}))
1212

13+
def convert_value(self, data, key):
14+
# all value in querydict as set in list
15+
value = data[key]
16+
if isinstance(value, list):
17+
return value[0]
18+
return value
19+
1320
@property
1421
def validate_data(self):
1522
dtc = QueryDict(mutable=True)
@@ -21,14 +28,9 @@ def validate_data(self):
2128
class DrfNestedParser(MultiPartParser):
2229

2330
def parse(self, stream, media_type=None, parser_context=None):
24-
parsed = super().parse(stream, media_type, parser_context)
31+
clsDataAndFile = super().parse(stream, media_type, parser_context)
2532

26-
copy = parsed.data.copy()
27-
if parsed.files:
28-
copy.update(parsed.files)
29-
parser = NestedParser(copy)
33+
parser = NestedParser(clsDataAndFile.data.dict())
3034
if parser.is_valid():
31-
return parser.validate_data
32-
if parser.errors:
33-
raise MultiPartParserError(parser.errors)
34-
return parsed
35+
return DataAndFiles(parser.validate_data, clsDataAndFile.files)
36+
raise ParseError(parser.errors)

nested_multipart_parser/parser.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ def split_key(self, key):
4343
length = 1
4444
splitter = k.split(".")
4545
else:
46-
splitter = self._reg.split(k)
4746
length = 2
47+
splitter = self._reg.split(k)
4848

4949
check = -length
5050

@@ -67,7 +67,7 @@ def set_type(self, dtc, key, value, full_keys, prev=None, last=False):
6767
if len(dtc) == key:
6868
dtc.append(value)
6969
elif isinstance(dtc, dict):
70-
if key not in dtc or self._options["assign_duplicate"] and last:
70+
if key not in dtc or last and self._options["assign_duplicate"]:
7171
dtc[key] = value
7272
else:
7373
if self._options["raise_duplicate"]:
@@ -82,32 +82,38 @@ def set_type(self, dtc, key, value, full_keys, prev=None, last=False):
8282
def get_next_type(self, keys):
8383
return [] if keys.isdigit() else {}
8484

85+
def convert_value(self, data, key):
86+
return data[key]
87+
8588
def construct(self, data):
8689
dictionary = {}
90+
prev = {}
8791

8892
for key in data:
8993
keys = self.split_key(key)
9094
tmp = dictionary
91-
prev = {
92-
'key': keys[0],
93-
'dtc': tmp,
94-
'type': None
95-
}
95+
96+
# need it for duplicate assignement
97+
prev['key'] = keys[0]
98+
prev['dtc'] = tmp
99+
prev['type'] = None
96100

97101
# optimize with while loop instend of for in with zip function
98102
i = 0
99103
lenght = len(keys) - 1
100104
while i < lenght:
101105
set_type = self.get_next_type(keys[i+1])
102-
index = self.set_type(
103-
tmp, keys[i], set_type, key, prev=prev)
106+
index = self.set_type(tmp, keys[i], set_type, key, prev)
107+
104108
prev['dtc'] = tmp
105109
prev['key'] = index
106110
prev['type'] = set_type
111+
107112
tmp = tmp[index]
108113
i += 1
109114

110-
self.set_type(tmp, keys[-1], data[key], key, prev=prev, last=True)
115+
value = self.convert_value(data, key)
116+
self.set_type(tmp, keys[-1], value, key, prev, True)
111117
return dictionary
112118

113119
def is_valid(self):

0 commit comments

Comments
 (0)