aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorBrickXu2014-12-02 12:52:36 +0800
committerBrickXu2014-12-02 12:52:36 +0800
commit81870b6e1a7b0c3c149d4bfba0e20924ebf1b187 (patch)
tree4acb295a3c941db00b36f54f906d4e4e24e5bcf4 /rest_framework
parent71654967325ff5c587ce37031109152e741786ee (diff)
parentd1fe61ce94af1e942f8d1026fb84b1909c230779 (diff)
downloaddjango-rest-framework-81870b6e1a7b0c3c149d4bfba0e20924ebf1b187.tar.bz2
Merge pull request #2 from tomchristie/master
Merge upstream
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/authtoken/views.py14
-rw-r--r--rest_framework/decorators.py4
-rw-r--r--rest_framework/fields.py14
-rw-r--r--rest_framework/relations.py19
-rw-r--r--rest_framework/request.py2
-rw-r--r--rest_framework/response.py4
-rw-r--r--rest_framework/serializers.py72
-rw-r--r--rest_framework/utils/model_meta.py7
8 files changed, 101 insertions, 35 deletions
diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py
index 103abb27..b75c2e25 100644
--- a/rest_framework/authtoken/views.py
+++ b/rest_framework/authtoken/views.py
@@ -1,5 +1,4 @@
from rest_framework.views import APIView
-from rest_framework import status
from rest_framework import parsers
from rest_framework import renderers
from rest_framework.response import Response
@@ -12,16 +11,13 @@ class ObtainAuthToken(APIView):
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
- serializer_class = AuthTokenSerializer
- model = Token
def post(self, request):
- serializer = self.serializer_class(data=request.data)
- if serializer.is_valid():
- user = serializer.validated_data['user']
- token, created = Token.objects.get_or_create(user=user)
- return Response({'token': token.key})
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+ serializer = AuthTokenSerializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+ user = serializer.validated_data['user']
+ token, created = Token.objects.get_or_create(user=user)
+ return Response({'token': token.key})
obtain_auth_token = ObtainAuthToken.as_view()
diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py
index d28d6e22..325435b3 100644
--- a/rest_framework/decorators.py
+++ b/rest_framework/decorators.py
@@ -12,12 +12,14 @@ from rest_framework.views import APIView
import types
-def api_view(http_method_names):
+def api_view(http_method_names=None):
"""
Decorator that converts a function-based view into an APIView subclass.
Takes a list of allowed methods for the view as an argument.
"""
+ if http_method_names is None:
+ http_method_names = ['GET']
def decorator(func):
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 778bc718..ca9c479f 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -68,8 +68,8 @@ def get_attribute(instance, attrs):
return instance[attr]
except (KeyError, TypeError, AttributeError):
raise exc
- if is_simple_callable(instance):
- return instance()
+ if is_simple_callable(instance):
+ instance = instance()
return instance
@@ -262,7 +262,11 @@ class Field(object):
if html.is_html_input(dictionary):
# HTML forms will represent empty fields as '', and cannot
# represent None or False values directly.
- ret = dictionary.get(self.field_name, '')
+ if self.field_name not in dictionary:
+ if getattr(self.root, 'partial', False):
+ return empty
+ return self.default_empty_html
+ ret = dictionary[self.field_name]
return self.default_empty_html if (ret == '') else ret
return dictionary.get(self.field_name, empty)
@@ -317,7 +321,6 @@ class Field(object):
value = self.to_internal_value(data)
self.run_validators(value)
- self.validate(value)
return value
def run_validators(self, value):
@@ -344,9 +347,6 @@ class Field(object):
if errors:
raise ValidationError(errors)
- def validate(self, value):
- pass
-
def to_internal_value(self, data):
"""
Transform the *incoming* primitive data into a native value.
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 79c8057b..d1ea497a 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -49,6 +49,21 @@ class RelatedField(Field):
@classmethod
def many_init(cls, *args, **kwargs):
+ """
+ This method handles creating a parent `ManyRelatedField` instance
+ when the `many=True` keyword argument is passed.
+
+ Typically you won't need to override this method.
+
+ Note that we're over-cautious in passing most arguments to both parent
+ and child classes in order to try to cover the general case. If you're
+ overriding this method you'll probably want something much simpler, eg:
+
+ @classmethod
+ def many_init(cls, *args, **kwargs):
+ kwargs['child'] = cls()
+ return CustomManyRelatedField(*args, **kwargs)
+ """
list_kwargs = {'child_relation': cls(*args, **kwargs)}
for key in kwargs.keys():
if key in MANY_RELATION_KWARGS:
@@ -306,7 +321,9 @@ class ManyRelatedField(Field):
The `ManyRelatedField` class is responsible for handling iterating through
the values and passing each one to the child relationship.
- You shouldn't need to be using this class directly yourself.
+ This class is treated as private API.
+ You shouldn't generally need to be using this class directly yourself,
+ and should instead simply set 'many=True' on the relationship.
"""
initial = []
default_empty_html = []
diff --git a/rest_framework/request.py b/rest_framework/request.py
index 096b3042..d7e74674 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -310,7 +310,7 @@ class Request(object):
def _load_data_and_files(self):
"""
- Parses the request content into self.DATA and self.FILES.
+ Parses the request content into `self.data`.
"""
if not _hasattr(self, '_content_type'):
self._load_method_and_content_type()
diff --git a/rest_framework/response.py b/rest_framework/response.py
index 0a7d313f..d6ca1aad 100644
--- a/rest_framework/response.py
+++ b/rest_framework/response.py
@@ -5,7 +5,6 @@ it is initialized with unrendered data, instead of a pre-rendered string.
The appropriate renderer is called during Django's template response rendering.
"""
from __future__ import unicode_literals
-import django
from django.core.handlers.wsgi import STATUS_CODE_TEXT
from django.template.response import SimpleTemplateResponse
from django.utils import six
@@ -16,9 +15,6 @@ class Response(SimpleTemplateResponse):
An HttpResponse that allows its data to be rendered into
arbitrary media types.
"""
- # TODO: remove that once Django 1.3 isn't supported
- if django.VERSION >= (1, 4):
- rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_closable_objects']
def __init__(self, data=None, status=None,
template_name=None, headers=None,
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 2d5c843e..0d0a4d9a 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -11,6 +11,7 @@ python primitives.
response content is handled by parsers and renderers.
"""
from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.utils import six
@@ -86,6 +87,15 @@ class BaseSerializer(Field):
class when `many=True` is used. You can customize it if you need to
control which keyword arguments are passed to the parent, and
which are passed to the child.
+
+ Note that we're over-cautious in passing most arguments to both parent
+ and child classes in order to try to cover the general case. If you're
+ overriding this method you'll probably want something much simpler, eg:
+
+ @classmethod
+ def many_init(cls, *args, **kwargs):
+ kwargs['child'] = cls()
+ return CustomListSerializer(*args, **kwargs)
"""
child_serializer = cls(*args, **kwargs)
list_kwargs = {'child': child_serializer}
@@ -93,7 +103,9 @@ class BaseSerializer(Field):
(key, value) for key, value in kwargs.items()
if key in LIST_SERIALIZER_KWARGS
]))
- return ListSerializer(*args, **list_kwargs)
+ meta = getattr(cls, 'Meta', None)
+ list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
+ return list_serializer_class(*args, **list_kwargs)
def to_internal_value(self, data):
raise NotImplementedError('`to_internal_value()` must be implemented.')
@@ -319,6 +331,14 @@ class Serializer(BaseSerializer):
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
})
+ except DjangoValidationError as exc:
+ # Normally you should raise `serializers.ValidationError`
+ # inside your codebase, but we handle Django's validation
+ # exception class as well for simpler compat.
+ # Eg. Calling Model.clean() explictily inside Serializer.validate()
+ raise ValidationError({
+ api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
+ })
return value
@@ -342,6 +362,8 @@ class Serializer(BaseSerializer):
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
+ except DjangoValidationError as exc:
+ errors[field.field_name] = list(exc.messages)
except SkipField:
pass
else:
@@ -362,14 +384,9 @@ class Serializer(BaseSerializer):
for field in fields:
attribute = field.get_attribute(instance)
if attribute is None:
- value = None
+ ret[field.field_name] = None
else:
- value = field.to_representation(attribute)
- transform_method = getattr(self, 'transform_' + field.field_name, None)
- if transform_method is not None:
- value = transform_method(value)
-
- ret[field.field_name] = value
+ ret[field.field_name] = field.to_representation(attribute)
return ret
@@ -548,6 +565,14 @@ class ModelSerializer(Serializer):
* A set of default fields are automatically populated.
* A set of default validators are automatically populated.
* Default `.create()` and `.update()` implementations are provided.
+
+ The process of automatically determining a set of serializer fields
+ based on the model fields is reasonably complex, but you almost certainly
+ don't need to dig into the implemention.
+
+ If the `ModelSerializer` class *doesn't* generate the set of fields that
+ you need you should either declare the extra/differing fields explicitly on
+ the serializer class, or simply use a `Serializer` class.
"""
_field_mapping = ClassLookupDict({
models.AutoField: IntegerField,
@@ -576,6 +601,26 @@ class ModelSerializer(Serializer):
_related_class = PrimaryKeyRelatedField
def create(self, validated_attrs):
+ """
+ We have a bit of extra checking around this in order to provide
+ descriptive messages when something goes wrong, but this method is
+ essentially just:
+
+ return ExampleModel.objects.create(**validated_attrs)
+
+ If there are many to many fields present on the instance then they
+ cannot be set until the model is instantiated, in which case the
+ implementation is like so:
+
+ example_relationship = validated_attrs.pop('example_relationship')
+ instance = ExampleModel.objects.create(**validated_attrs)
+ instance.example_relationship = example_relationship
+ return instance
+
+ The default implementation also does not handle nested relationships.
+ If you want to support writable nested relationships you'll need
+ to write an explicit `.create()` method.
+ """
# Check that the user isn't trying to handle a writable nested field.
# If we don't do this explicitly they'd likely get a confusing
# error at the point of calling `Model.objects.create()`.
@@ -626,14 +671,19 @@ class ModelSerializer(Serializer):
return instance
def get_validators(self):
+ # If the validators have been declared explicitly then use that.
+ validators = getattr(getattr(self, 'Meta', None), 'validators', None)
+ if validators is not None:
+ return validators
+
+ # Determine the default set of validators.
+ validators = []
+ model_class = self.Meta.model
field_names = set([
field.source for field in self.fields.values()
if (field.source != '*') and ('.' not in field.source)
])
- validators = getattr(getattr(self, 'Meta', None), 'validators', [])
- model_class = self.Meta.model
-
# Note that we make sure to check `unique_together` both on the
# base model class, but also on any parent classes.
for parent_class in [model_class] + list(model_class._meta.parents.keys()):
diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py
index 82361edf..c98725c6 100644
--- a/rest_framework/utils/model_meta.py
+++ b/rest_framework/utils/model_meta.py
@@ -6,6 +6,7 @@ relationships and their associated metadata.
Usage: `get_field_info(model)` returns a `FieldInfo` instance.
"""
from collections import namedtuple
+from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils import six
from rest_framework.compat import OrderedDict
@@ -43,7 +44,11 @@ def _resolve_model(obj):
"""
if isinstance(obj, six.string_types) and len(obj.split('.')) == 2:
app_name, model_name = obj.split('.')
- return models.get_model(app_name, model_name)
+ resolved_model = models.get_model(app_name, model_name)
+ if resolved_model is None:
+ msg = "Django did not return a model for {0}.{1}"
+ raise ImproperlyConfigured(msg.format(app_name, model_name))
+ return resolved_model
elif inspect.isclass(obj) and issubclass(obj, models.Model):
return obj
raise ValueError("{0} is not a Django model".format(obj))