From 311d315a739f4d1d02e87a09de0bbf9e7b0cee46 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Wed, 8 Oct 2014 08:33:28 +0200 Subject: Reverted 59d0a0387d907260ef8f91bbbca618831abd75a3 and fixed the tests --- rest_framework/response.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'rest_framework') 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, -- cgit v1.2.3 From 67735687b297e66c7cfe61614040efcd9763f1f1 Mon Sep 17 00:00:00 2001 From: Doug Beck Date: Tue, 18 Nov 2014 01:26:23 -0500 Subject: Ensure `_resolve_model` does not return `None` --- rest_framework/utils/model_meta.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py index 82361edf..54f9310d 100644 --- a/rest_framework/utils/model_meta.py +++ b/rest_framework/utils/model_meta.py @@ -43,7 +43,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 not resolved_model: + raise ValueError("Django did not return a model for " + "{0}.{1}".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)) -- cgit v1.2.3 From fd980be39be7f09d4cf0ceb16688ad0157d4df35 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 10:39:58 +0000 Subject: Documentation in 'many_init' docstring. Refs #2120. --- rest_framework/serializers.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2d5c843e..00362dbb 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -86,6 +86,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} -- cgit v1.2.3 From 06fd63dade20e1a19276b7414a54b9f5d2ef8329 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 11:14:28 +0000 Subject: Don't use default_empty_html value for partial updates. Closes #2118. --- rest_framework/fields.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 778bc718..3cf34886 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -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) -- cgit v1.2.3 From 2e726e22a394347b7337eb38a2a3a1b0ccde88bc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 11:42:43 +0000 Subject: request.DATA, request.FILES -> request.data --- rest_framework/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') 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() -- cgit v1.2.3 From b4550c65dd339731ff3dd2b0dd3c6dad12565906 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Nov 2014 11:30:28 +0000 Subject: Docstring for 'many_init' --- rest_framework/relations.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 79c8057b..5a3403f5 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: -- cgit v1.2.3 From 786cab705a37f6b2196560d0e476e5c849420a61 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Nov 2014 11:38:48 +0000 Subject: Tweak to 3.0 relationship docs --- rest_framework/relations.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 5a3403f5..d1ea497a 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -321,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 = [] -- cgit v1.2.3 From 1ffe4857ec51c3b6c5a90059cca0fb82820d759e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Nov 2014 08:10:52 +0000 Subject: Support callable attributes in dotted source. Closes #2142. --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 3cf34886..ca770f77 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): + return instance() return instance -- cgit v1.2.3 From 8579222cbdff98aef50e244e179b7c32a76ce8ad Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Nov 2014 15:14:11 +0000 Subject: Fix for nested attribute lookups where one is a callable --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index ca770f77..2e2a68b8 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -69,7 +69,7 @@ def get_attribute(instance, attrs): except (KeyError, TypeError, AttributeError): raise exc if is_simple_callable(instance): - return instance() + instance = instance() return instance -- cgit v1.2.3 From 6b2033f7894319c827c04a5caded7187797fc0b3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Nov 2014 16:40:58 +0000 Subject: Drop transform_ --- rest_framework/serializers.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 00362dbb..e86e67f7 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -371,14 +371,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 -- cgit v1.2.3 From d2d7e1dfde2a62ee8f6d904368dbd6581de278c9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Nov 2014 16:41:53 +0000 Subject: Drop Field.validate --- rest_framework/fields.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 2e2a68b8..ca9c479f 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -321,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): @@ -348,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. -- cgit v1.2.3 From 34ca8cd2a5c030d9acc89720876ba9583c1dc988 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 28 Nov 2014 09:56:44 +0000 Subject: Moar docs. Amazing. --- rest_framework/serializers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 00362dbb..14078e64 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -102,7 +102,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.') -- cgit v1.2.3 From 0539b1be01fd9c6b842a4a8bbdf23b65af8357d1 Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Fri, 28 Nov 2014 12:27:05 +0100 Subject: Update token auth view. Closes #2151. --- rest_framework/authtoken/views.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py index 103abb27..1fee5068 100644 --- a/rest_framework/authtoken/views.py +++ b/rest_framework/authtoken/views.py @@ -12,16 +12,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() -- cgit v1.2.3 From d9a09f7826d7faf7e6bfa10fc83e0b7db4689a72 Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Fri, 28 Nov 2014 13:33:07 +0100 Subject: Removed unused import --- rest_framework/authtoken/views.py | 1 - 1 file changed, 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py index 1fee5068..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 -- cgit v1.2.3 From 3a5b3772fefc3c2f2c0899947cbc07bfe6e6b5d2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 28 Nov 2014 15:36:04 +0000 Subject: Use ImproperlyConfigured when model meta lookup fails --- rest_framework/utils/model_meta.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py index 54f9310d..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 @@ -44,9 +45,9 @@ def _resolve_model(obj): if isinstance(obj, six.string_types) and len(obj.split('.')) == 2: app_name, model_name = obj.split('.') resolved_model = models.get_model(app_name, model_name) - if not resolved_model: - raise ValueError("Django did not return a model for " - "{0}.{1}".format(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 -- cgit v1.2.3 From 08c727add37790b5a556db3fff762f4a27a5c660 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 28 Nov 2014 15:55:02 +0000 Subject: @api_view defaults to allowing GET --- rest_framework/decorators.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'rest_framework') 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): -- cgit v1.2.3 From b9503cd603613e4ae72b7718ba70a00b1537b289 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 1 Dec 2014 10:48:45 +0000 Subject: Support Django's core ValidationError for backwards compat. Refs #2145. --- rest_framework/serializers.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index f7aa3a7d..de0d026d 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 @@ -330,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 @@ -353,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: -- cgit v1.2.3 From 22c5b863bc395a6df84ec2536849540d3e92da7a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 1 Dec 2014 11:37:38 +0000 Subject: More descriptive docstring on ModelSerializer --- rest_framework/serializers.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index de0d026d..3783b285 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -565,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, -- cgit v1.2.3 From 270c7acdd75e9dd0b7a600e5648b0ec364c9a510 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 1 Dec 2014 11:59:04 +0000 Subject: Minor validtors tweak --- rest_framework/serializers.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 3783b285..0d0a4d9a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -601,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()`. @@ -651,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()): -- cgit v1.2.3