diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/__init__.py | 2 | ||||
| -rw-r--r-- | rest_framework/authtoken/serializers.py | 11 | ||||
| -rw-r--r-- | rest_framework/compat.py | 1 | ||||
| -rw-r--r-- | rest_framework/fields.py | 23 | ||||
| -rw-r--r-- | rest_framework/generics.py | 8 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 30 | ||||
| -rw-r--r-- | rest_framework/templatetags/rest_framework.py | 4 | ||||
| -rw-r--r-- | rest_framework/test.py | 2 | ||||
| -rw-r--r-- | rest_framework/throttling.py | 6 |
9 files changed, 60 insertions, 27 deletions
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 2d76b55d..01036cef 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ ______ _____ _____ _____ __ _ """ __title__ = 'Django REST framework' -__version__ = '2.3.13' +__version__ = '2.3.14' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2014 Tom Christie' diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index 60a3740e..99e99ae3 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -1,4 +1,6 @@ from django.contrib.auth import authenticate +from django.utils.translation import ugettext_lazy as _ + from rest_framework import serializers @@ -15,10 +17,13 @@ class AuthTokenSerializer(serializers.Serializer): if user: if not user.is_active: - raise serializers.ValidationError('User account is disabled.') + msg = _('User account is disabled.') + raise serializers.ValidationError(msg) attrs['user'] = user return attrs else: - raise serializers.ValidationError('Unable to login with provided credentials.') + msg = _('Unable to login with provided credentials.') + raise serializers.ValidationError(msg) else: - raise serializers.ValidationError('Must include "username" and "password"') + msg = _('Must include "username" and "password"') + raise serializers.ValidationError(msg) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index a013a155..6a5cbbe4 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -47,6 +47,7 @@ except ImportError: # django-guardian is optional try: import guardian + import guardian.shortcuts # Fixes #1624 except ImportError: guardian = None diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 8eaf763d..e925c47e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -64,7 +64,7 @@ def get_component(obj, attr_name): def readable_datetime_formats(formats): format = ', '.join(formats).replace(ISO_8601, - 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]') + 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]') return humanize_strptime(format) @@ -156,7 +156,12 @@ class Field(object): def widget_html(self): if not self.widget: return '' - return self.widget.render(self._name, self._value) + + attrs = {} + if 'id' not in self.widget.attrs: + attrs['id'] = self._name + + return self.widget.render(self._name, self._value, attrs=attrs) def label_tag(self): return '<label for="%s">%s:</label>' % (self._name, self.label) @@ -499,7 +504,7 @@ class SlugField(CharField): class ChoiceField(WritableField): type_name = 'ChoiceField' - type_label = 'multiple choice' + type_label = 'choice' form_field_class = forms.ChoiceField widget = widgets.Select default_error_messages = { @@ -507,12 +512,16 @@ class ChoiceField(WritableField): 'the available choices.'), } - def __init__(self, choices=(), *args, **kwargs): + def __init__(self, choices=(), blank_display_value=None, *args, **kwargs): self.empty = kwargs.pop('empty', '') super(ChoiceField, self).__init__(*args, **kwargs) self.choices = choices if not self.required: - self.choices = BLANK_CHOICE_DASH + self.choices + if blank_display_value is None: + blank_choice = BLANK_CHOICE_DASH + else: + blank_choice = [('', blank_display_value)] + self.choices = blank_choice + self.choices def _get_choices(self): return self._choices @@ -1016,9 +1025,9 @@ class SerializerMethodField(Field): A field that gets its value by calling a method on the serializer it's attached to. """ - def __init__(self, method_name): + def __init__(self, method_name, *args, **kwargs): self.method_name = method_name - super(SerializerMethodField, self).__init__() + super(SerializerMethodField, self).__init__(*args, **kwargs) def field_to_native(self, obj, field_name): value = getattr(self.parent, self.method_name)(obj) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index c3256844..e38c52b1 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -90,8 +90,8 @@ class GenericAPIView(views.APIView): 'view': self } - def get_serializer(self, instance=None, data=None, - files=None, many=False, partial=False): + def get_serializer(self, instance=None, data=None, files=None, many=False, + partial=False, allow_add_remove=False): """ Return the serializer instance that should be used for validating and deserializing input, and for serializing output. @@ -99,7 +99,9 @@ class GenericAPIView(views.APIView): serializer_class = self.get_serializer_class() context = self.get_serializer_context() return serializer_class(instance, data=data, files=files, - many=many, partial=partial, context=context) + many=many, partial=partial, + allow_add_remove=allow_add_remove, + context=context) def get_pagination_serializer(self, page): """ diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 9709d56d..a15b8f22 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -21,6 +21,7 @@ from django.core.paginator import Page from django.db import models from django.forms import widgets from django.utils.datastructures import SortedDict +from django.core.exceptions import ObjectDoesNotExist from rest_framework.compat import six from rest_framework.settings import api_settings @@ -32,8 +33,8 @@ from rest_framework.settings import api_settings # This helps keep the separation between model fields, form fields, and # serializer fields more explicit. -from rest_framework.relations import * -from rest_framework.fields import * +from rest_framework.relations import * # NOQA +from rest_framework.fields import * # NOQA def _resolve_model(obj): @@ -48,7 +49,7 @@ def _resolve_model(obj): String representations should have the format: 'appname.ModelName' """ - if type(obj) == str and len(obj.split('.')) == 2: + 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) elif inspect.isclass(obj) and issubclass(obj, models.Model): @@ -344,7 +345,7 @@ class BaseSerializer(WritableField): for field_name, field in self.fields.items(): if field.read_only and obj is None: - continue + continue field.initialize(parent=self, field_name=field_name) key = self.get_field_key(field_name) value = field.field_to_native(obj, field_name) @@ -753,9 +754,9 @@ class ModelSerializer(Serializer): field.read_only = True ret[accessor_name] = field - + # Ensure that 'read_only_fields' is an iterable - assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple' + assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple' # Add the `read_only` flag to any fields that have been specified # in the `read_only_fields` option @@ -770,10 +771,10 @@ class ModelSerializer(Serializer): "on serializer '%s'." % (field_name, self.__class__.__name__)) ret[field_name].read_only = True - + # Ensure that 'write_only_fields' is an iterable - assert isinstance(self.opts.write_only_fields, (list, tuple)), '`write_only_fields` must be a list or tuple' - + assert isinstance(self.opts.write_only_fields, (list, tuple)), '`write_only_fields` must be a list or tuple' + for field_name in self.opts.write_only_fields: assert field_name not in self.base_fields.keys(), ( "field '%s' on serializer '%s' specified in " @@ -784,7 +785,7 @@ class ModelSerializer(Serializer): "Non-existant field '%s' specified in `write_only_fields` " "on serializer '%s'." % (field_name, self.__class__.__name__)) - ret[field_name].write_only = True + ret[field_name].write_only = True return ret @@ -828,6 +829,15 @@ class ModelSerializer(Serializer): if model_field.verbose_name is not None: kwargs['label'] = model_field.verbose_name + if not model_field.editable: + kwargs['read_only'] = True + + if model_field.verbose_name is not None: + kwargs['label'] = model_field.verbose_name + + if model_field.help_text is not None: + kwargs['help_text'] = model_field.help_text + return PrimaryKeyRelatedField(**kwargs) def get_field(self, model_field): diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 886db63c..911b1b62 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -40,7 +40,7 @@ def optional_login(request): except NoReverseMatch: return '' - snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, request.path) + snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, escape(request.path)) return snippet @@ -54,7 +54,7 @@ def optional_logout(request): except NoReverseMatch: return '' - snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, request.path) + snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, escape(request.path)) return snippet diff --git a/rest_framework/test.py b/rest_framework/test.py index df5a5b3b..284bcee0 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -36,7 +36,7 @@ class APIRequestFactory(DjangoRequestFactory): """ if not data: - return ('', None) + return ('', content_type) assert format is None or content_type is None, ( 'You may not set both `format` and `content_type`.' diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index fc24c92e..7e9f9d71 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -173,6 +173,12 @@ class AnonRateThrottle(SimpleRateThrottle): if request.user.is_authenticated(): return None # Only throttle unauthenticated requests. + ident = request.META.get('HTTP_X_FORWARDED_FOR') + if ident is None: + ident = request.META.get('REMOTE_ADDR') + else: + ident = ''.join(ident.split()) + return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) |
