diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/__init__.py | 2 | ||||
| -rw-r--r-- | rest_framework/authtoken/south_migrations/0001_initial.py | 2 | ||||
| -rw-r--r-- | rest_framework/compat.py | 7 | ||||
| -rw-r--r-- | rest_framework/fields.py | 77 | ||||
| -rw-r--r-- | rest_framework/parsers.py | 2 | ||||
| -rw-r--r-- | rest_framework/relations.py | 5 | ||||
| -rw-r--r-- | rest_framework/routers.py | 10 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 12 | ||||
| -rw-r--r-- | rest_framework/settings.py | 11 | ||||
| -rw-r--r-- | rest_framework/utils/field_mapping.py | 3 |
10 files changed, 117 insertions, 14 deletions
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index fdcebb7b..57e5421b 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ ______ _____ _____ _____ __ """ __title__ = 'Django REST framework' -__version__ = '3.0.3' +__version__ = '3.0.4' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2015 Tom Christie' diff --git a/rest_framework/authtoken/south_migrations/0001_initial.py b/rest_framework/authtoken/south_migrations/0001_initial.py index 926de02b..5b927f3e 100644 --- a/rest_framework/authtoken/south_migrations/0001_initial.py +++ b/rest_framework/authtoken/south_migrations/0001_initial.py @@ -40,7 +40,7 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, "%s.%s" % (User._meta.app_label, User._meta.module_name): { - 'Meta': {'object_name': User._meta.module_name}, + 'Meta': {'object_name': User._meta.module_name, 'db_table': repr(User._meta.db_table)}, }, 'authtoken.token': { 'Meta': {'object_name': 'Token'}, diff --git a/rest_framework/compat.py b/rest_framework/compat.py index a6620057..50f37014 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -63,6 +63,13 @@ except ImportError: from django.http import HttpResponse as HttpResponseBase +# contrib.postgres only supported from 1.8 onwards. +try: + from django.contrib.postgres import fields as postgres_fields +except ImportError: + postgres_fields = None + + # request only provides `resolver_match` from 1.5 onwards. def get_resolver_match(request): try: diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 342564d3..02d2adef 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -23,6 +23,7 @@ import datetime import decimal import inspect import re +import uuid class empty: @@ -632,6 +633,23 @@ class URLField(CharField): self.validators.append(validator) +class UUIDField(Field): + default_error_messages = { + 'invalid': _('"{value}" is not a valid UUID.'), + } + + def to_internal_value(self, data): + if not isinstance(data, uuid.UUID): + try: + return uuid.UUID(data) + except (ValueError, TypeError): + self.fail('invalid', value=data) + return data + + def to_representation(self, value): + return str(value) + + # Number types... class IntegerField(Field): @@ -1113,8 +1131,21 @@ class ImageField(FileField): # Composite field types... +class _UnvalidatedField(Field): + def __init__(self, *args, **kwargs): + super(_UnvalidatedField, self).__init__(*args, **kwargs) + self.allow_blank = True + self.allow_null = True + + def to_internal_value(self, data): + return data + + def to_representation(self, value): + return value + + class ListField(Field): - child = None + child = _UnvalidatedField() initial = [] default_error_messages = { 'not_a_list': _('Expected a list of items but got type "{input_type}".') @@ -1122,7 +1153,6 @@ class ListField(Field): def __init__(self, *args, **kwargs): self.child = kwargs.pop('child', copy.deepcopy(self.child)) - assert self.child is not None, '`child` is a required argument.' assert not inspect.isclass(self.child), '`child` has not been instantiated.' super(ListField, self).__init__(*args, **kwargs) self.child.bind(field_name='', parent=self) @@ -1151,6 +1181,49 @@ class ListField(Field): return [self.child.to_representation(item) for item in data] +class DictField(Field): + child = _UnvalidatedField() + initial = [] + default_error_messages = { + 'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".') + } + + def __init__(self, *args, **kwargs): + self.child = kwargs.pop('child', copy.deepcopy(self.child)) + assert not inspect.isclass(self.child), '`child` has not been instantiated.' + super(DictField, self).__init__(*args, **kwargs) + self.child.bind(field_name='', parent=self) + + def get_value(self, dictionary): + # We override the default field access in order to support + # lists in HTML forms. + if html.is_html_input(dictionary): + return html.parse_html_list(dictionary, prefix=self.field_name) + return dictionary.get(self.field_name, empty) + + def to_internal_value(self, data): + """ + Dicts of native values <- Dicts of primitive datatypes. + """ + if html.is_html_input(data): + data = html.parse_html_dict(data) + if not isinstance(data, dict): + self.fail('not_a_dict', input_type=type(data).__name__) + return dict([ + (six.text_type(key), self.child.run_validation(value)) + for key, value in data.items() + ]) + + def to_representation(self, value): + """ + List of object instances -> List of dicts of primitive datatypes. + """ + return dict([ + (six.text_type(key), self.child.to_representation(val)) + for key, val in value.items() + ]) + + # Miscellaneous field types... class ReadOnlyField(Field): diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 7a51bb05..437d1339 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -152,7 +152,7 @@ class FileUploadParser(BaseParser): None, encoding) if result is not None: - return DataAndFiles(None, {'file': result[1]}) + return DataAndFiles({}, {'file': result[1]}) # This is the standard case. possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] diff --git a/rest_framework/relations.py b/rest_framework/relations.py index a85edfec..66857a41 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -338,7 +338,12 @@ class ManyRelatedField(Field): # We override the default field access in order to support # lists in HTML forms. if html.is_html_input(dictionary): + # Don't return [] if the update is partial + if self.field_name not in dictionary: + if getattr(self.root, 'partial', False): + return empty return dictionary.getlist(self.field_name) + return dictionary.get(self.field_name, empty) def to_internal_value(self, data): diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 827da034..6a4184e2 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -130,19 +130,13 @@ class SimpleRouter(BaseRouter): If `base_name` is not specified, attempt to automatically determine it from the viewset. """ - # Note that `.model` attribute on views is deprecated, although we - # enforce the deprecation on the view `get_serializer_class()` and - # `get_queryset()` methods, rather than here. - model_cls = getattr(viewset, 'model', None) queryset = getattr(viewset, 'queryset', None) - if model_cls is None and queryset is not None: - model_cls = queryset.model - assert model_cls, '`base_name` argument not specified, and could ' \ + assert queryset is not None, '`base_name` argument not specified, and could ' \ 'not automatically determine the name from the viewset, as ' \ 'it does not have a `.queryset` attribute.' - return model_cls._meta.object_name.lower() + return queryset.model._meta.object_name.lower() def get_routes(self, viewset): """ diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d9a67441..a3b8196b 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -14,7 +14,7 @@ from __future__ import unicode_literals from django.db import models from django.db.models.fields import FieldDoesNotExist, Field as DjangoModelField from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import unicode_to_repr +from rest_framework.compat import postgres_fields, unicode_to_repr from rest_framework.utils import model_meta from rest_framework.utils.field_mapping import ( get_url_kwargs, get_field_kwargs, @@ -1329,6 +1329,16 @@ class ModelSerializer(Serializer): return validators +if hasattr(models, 'UUIDField'): + ModelSerializer._field_mapping[models.UUIDField] = UUIDField + +if postgres_fields: + class CharMappingField(DictField): + child = CharField() + + ModelSerializer._field_mapping[postgres_fields.HStoreField] = CharMappingField + + class HyperlinkedModelSerializer(ModelSerializer): """ A type of `ModelSerializer` that uses hyperlinked relationships instead diff --git a/rest_framework/settings.py b/rest_framework/settings.py index ca5af86e..7331f265 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -18,6 +18,7 @@ REST framework settings, checking for user settings first, then falling back to the defaults. """ from __future__ import unicode_literals +from django.test.signals import setting_changed from django.conf import settings from django.utils import importlib, six from rest_framework import ISO_8601 @@ -207,3 +208,13 @@ class APISettings(object): api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS) + + +def reload_api_settings(*args, **kwargs): + global api_settings + setting, value = kwargs['setting'], kwargs['value'] + if setting == 'REST_FRAMEWORK': + api_settings = APISettings(value, DEFAULTS, IMPORT_STRINGS) + + +setting_changed.connect(reload_api_settings) diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index cba40d31..c97ec5d0 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -38,6 +38,9 @@ class ClassLookupDict(object): return self.mapping[cls] raise KeyError('Class %s not found in lookup.', cls.__name__) + def __setitem__(self, key, value): + self.mapping[key] = value + def needs_label(model_field, field_name): """ |
