diff --git a/rest_framework/request.py b/rest_framework/request.py index 0d88ebc7e..45d1a43e8 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -13,6 +13,7 @@ from django.conf import settings from django.http import QueryDict from django.http.multipartparser import parse_header from django.utils.datastructures import MultiValueDict +from rest_framework.utils.datastructures import DotExpandedDict from rest_framework import HTTP_HEADER_ENCODING from rest_framework import exceptions from rest_framework.compat import BytesIO @@ -152,6 +153,10 @@ class Request(object): """ if not _hasattr(self, '_data'): self._load_data_and_files() + + if api_settings.NESTED_FIELDS: + self._data = DotExpandedDict(self._data) + return self._data @property diff --git a/rest_framework/settings.py b/rest_framework/settings.py index beb511aca..4b75397b5 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -65,6 +65,10 @@ DEFAULTS = { 'anon': None, }, + # Nested (multi-dimensional) field names + 'NESTED_FIELDS': False, + 'NESTED_FIELD_TOKENIZER': '.', + # Pagination 'PAGINATE_BY': None, 'PAGINATE_BY_PARAM': None, diff --git a/rest_framework/utils/datastructures.py b/rest_framework/utils/datastructures.py new file mode 100755 index 000000000..b7bb3466b --- /dev/null +++ b/rest_framework/utils/datastructures.py @@ -0,0 +1,38 @@ +""" +Utility functions for reshaping datastructures +""" +from rest_framework.settings import api_settings +from django.http import QueryDict + +class DotExpandedDict(QueryDict): + """ + A special dictionary constructor that takes a dictionary in which the keys + may contain dots to specify inner dictionaries. It's confusing, but this + example should make sense. + + >>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \ + 'person.1.lastname': ['Willison'], \ + 'person.2.firstname': ['Adrian'], \ + 'person.2.lastname': ['Holovaty']}) + >>> d + {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}} + >>> d['person'] + {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}} + >>> d['person']['1'] + {'lastname': ['Willison'], 'firstname': ['Simon']} + + # Gotcha: Results are unpredictable if the dots are "uneven": + >>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1}) + {'c': 1} + """ + def __init__(self, key_to_list_mapping): + for k, v in key_to_list_mapping.items(): + current = self + bits = k.split(api_settings.NESTED_FIELD_TOKENIZER) + for bit in bits[:-1]: + current = current.setdefault(bit, {}) + # Now assign value to current position + try: + current[bits[-1]] = v + except TypeError: # Special-case if current isn't a dict. + current = {bits[-1]: v} \ No newline at end of file