aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorTom Christie2015-01-30 14:00:25 +0000
committerTom Christie2015-01-30 14:00:25 +0000
commit4ee4b4f2dc3cffd8246772bdf829a2a720493377 (patch)
tree95b514483e901c10bdeee0d66e60430002d5101a /rest_framework
parent8f33e39f62c59d06783dd3e6e7cf8d464ff08c5f (diff)
parent7cf9dea7f905ea6869148a68b4fa96cad0a347e8 (diff)
downloaddjango-rest-framework-4ee4b4f2dc3cffd8246772bdf829a2a720493377.tar.bz2
Merge master
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/authtoken/south_migrations/0001_initial.py2
-rw-r--r--rest_framework/compat.py7
-rw-r--r--rest_framework/fields.py77
-rw-r--r--rest_framework/parsers.py2
-rw-r--r--rest_framework/relations.py5
-rw-r--r--rest_framework/routers.py10
-rw-r--r--rest_framework/serializers.py12
-rw-r--r--rest_framework/settings.py11
-rw-r--r--rest_framework/utils/field_mapping.py3
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):
"""