diff options
| author | Xavier Ordoquy | 2014-06-23 14:02:45 +0200 | 
|---|---|---|
| committer | Xavier Ordoquy | 2014-06-23 14:02:45 +0200 | 
| commit | 2489e38a06f575aa144644eee683bd87f20186ef (patch) | |
| tree | fc52fd2280d3ef6d0dc69d53a3771ee5f499ab29 /rest_framework | |
| parent | 15c2c58b43a00ec29af99e0478b70eea57560fce (diff) | |
| parent | e11f41ebc4ef088a5849771dfda5a7fba4f82904 (diff) | |
| download | django-rest-framework-2489e38a06f575aa144644eee683bd87f20186ef.tar.bz2 | |
Merge remote-tracking branch 'origin/master' into 2.4.0
Conflicts:
	.travis.yml
	docs/api-guide/viewsets.md
	rest_framework/serializers.py
	rest_framework/throttling.py
	tests/test_generics.py
	tests/test_serializers.py
	tox.ini
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)  | 
