diff options
| author | Serhiy Voyt | 2014-06-27 18:10:50 +0300 | 
|---|---|---|
| committer | Serhiy Voyt | 2014-06-27 18:10:50 +0300 | 
| commit | 3326ddc865154b0c09fc3bb0da2644f0dfc3bc24 (patch) | |
| tree | 6df4c5adf05460fc2949e74efbc57dec80d789dc /rest_framework | |
| parent | 4e6a21344fd1fda28d1d63c5aa697fac5e9f8029 (diff) | |
| parent | 636ae419be2882dbec8151b6baa60bde118faede (diff) | |
| download | django-rest-framework-3326ddc865154b0c09fc3bb0da2644f0dfc3bc24.tar.bz2 | |
Merge branch 'master' into modelserialization-charfield-with-null
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/__init__.py | 2 | ||||
| -rw-r--r-- | rest_framework/compat.py | 1 | ||||
| -rw-r--r-- | rest_framework/fields.py | 25 | ||||
| -rw-r--r-- | rest_framework/generics.py | 8 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 21 | ||||
| -rw-r--r-- | rest_framework/templatetags/rest_framework.py | 4 | ||||
| -rw-r--r-- | rest_framework/test.py | 2 | ||||
| -rw-r--r-- | rest_framework/tests/test_fields.py | 24 | ||||
| -rw-r--r-- | rest_framework/tests/test_serializers.py | 5 | ||||
| -rw-r--r-- | rest_framework/tests/test_views.py | 16 | 
10 files changed, 76 insertions, 32 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/compat.py b/rest_framework/compat.py index d155f554..fdf12448 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -51,6 +51,7 @@ except ImportError:  # 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 aed38d5e..6caae924 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -62,7 +62,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) @@ -154,7 +154,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) @@ -182,7 +187,7 @@ class Field(object):      def field_to_native(self, obj, field_name):          """ -        Given and object and a field name, returns the value that should be +        Given an object and a field name, returns the value that should be          serialized for that field.          """          if obj is None: @@ -505,7 +510,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 = { @@ -513,12 +518,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 @@ -1022,9 +1031,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 7bac510f..7fc9db36 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 2a0d5263..c2b414d7 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 get_concrete_model, 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) @@ -758,9 +759,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 @@ -775,10 +776,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 " @@ -789,7 +790,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 diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index dff176d6..a155d8d2 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -122,7 +122,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 @@ -136,7 +136,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/tests/test_fields.py b/rest_framework/tests/test_fields.py index e127feef..b04b947f 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -4,6 +4,7 @@ General serializer field tests.  from __future__ import unicode_literals  import datetime +import re  from decimal import Decimal  from uuid import uuid4  from django.core import validators @@ -103,6 +104,16 @@ class BasicFieldTests(TestCase):          keys = list(field.to_native(ret).keys())          self.assertEqual(keys, ['c', 'b', 'a', 'z']) +    def test_widget_html_attributes(self): +        """ +        Make sure widget_html() renders the correct attributes +        """ +        r = re.compile('(\S+)=["\']?((?:.(?!["\']?\s+(?:\S+)=|[>"\']))+.)["\']?') +        form = TimeFieldModelSerializer().data +        attributes = r.findall(form.fields['clock'].widget_html()) +        self.assertIn(('name', 'clock'), attributes) +        self.assertIn(('id', 'clock'), attributes) +  class DateFieldTest(TestCase):      """ @@ -312,7 +323,7 @@ class DateTimeFieldTest(TestCase):              f.from_native('04:61:59')          except validators.ValidationError as e:              self.assertEqual(e.messages, ["Datetime has wrong format. Use one of these formats instead: " -                                          "YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]"]) +                                          "YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]"])          else:              self.fail("ValidationError was not properly raised") @@ -326,7 +337,7 @@ class DateTimeFieldTest(TestCase):              f.from_native('04 -- 31')          except validators.ValidationError as e:              self.assertEqual(e.messages, ["Datetime has wrong format. Use one of these formats instead: " -                                          "YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]"]) +                                          "YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]"])          else:              self.fail("ValidationError was not properly raised") @@ -706,6 +717,15 @@ class ChoiceFieldTests(TestCase):          f = serializers.ChoiceField(required=False, choices=SAMPLE_CHOICES)          self.assertEqual(f.choices, models.fields.BLANK_CHOICE_DASH + SAMPLE_CHOICES) +    def test_blank_choice_display(self): +        blank = 'No Preference' +        f = serializers.ChoiceField( +            required=False, +            choices=SAMPLE_CHOICES, +            blank_display_value=blank, +        ) +        self.assertEqual(f.choices, [('', blank)] + SAMPLE_CHOICES) +      def test_invalid_choice_model(self):          s = ChoiceFieldModelSerializer(data={'choice': 'wrong_value'})          self.assertFalse(s.is_valid()) diff --git a/rest_framework/tests/test_serializers.py b/rest_framework/tests/test_serializers.py index 082a400c..120510ac 100644 --- a/rest_framework/tests/test_serializers.py +++ b/rest_framework/tests/test_serializers.py @@ -3,6 +3,7 @@ from django.test import TestCase  from rest_framework.serializers import _resolve_model  from rest_framework.tests.models import BasicModel +from rest_framework.compat import six  class ResolveModelTests(TestCase): @@ -19,6 +20,10 @@ class ResolveModelTests(TestCase):          resolved_model = _resolve_model('tests.BasicModel')          self.assertEqual(resolved_model, BasicModel) +    def test_resolve_unicode_representation(self): +        resolved_model = _resolve_model(six.text_type('tests.BasicModel')) +        self.assertEqual(resolved_model, BasicModel) +      def test_resolve_non_django_model(self):          with self.assertRaises(ValueError):              _resolve_model(TestCase) diff --git a/rest_framework/tests/test_views.py b/rest_framework/tests/test_views.py index 65c7e50e..77b113ee 100644 --- a/rest_framework/tests/test_views.py +++ b/rest_framework/tests/test_views.py @@ -1,5 +1,6 @@  from __future__ import unicode_literals +import sys  import copy  from django.test import TestCase  from rest_framework import status @@ -11,6 +12,11 @@ from rest_framework.views import APIView  factory = APIRequestFactory() +if sys.version_info[:2] >= (3, 4): +    JSON_ERROR = 'JSON parse error - Expecting value:' +else: +    JSON_ERROR = 'JSON parse error - No JSON object could be decoded' +  class BasicView(APIView):      def get(self, request, *args, **kwargs): @@ -48,7 +54,7 @@ def sanitise_json_error(error_dict):      of json.      """      ret = copy.copy(error_dict) -    chop = len('JSON parse error - No JSON object could be decoded') +    chop = len(JSON_ERROR)      ret['detail'] = ret['detail'][:chop]      return ret @@ -61,7 +67,7 @@ class ClassBasedViewIntegrationTests(TestCase):          request = factory.post('/', 'f00bar', content_type='application/json')          response = self.view(request)          expected = { -            'detail': 'JSON parse error - No JSON object could be decoded' +            'detail': JSON_ERROR          }          self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)          self.assertEqual(sanitise_json_error(response.data), expected) @@ -76,7 +82,7 @@ class ClassBasedViewIntegrationTests(TestCase):          request = factory.post('/', form_data)          response = self.view(request)          expected = { -            'detail': 'JSON parse error - No JSON object could be decoded' +            'detail': JSON_ERROR          }          self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)          self.assertEqual(sanitise_json_error(response.data), expected) @@ -90,7 +96,7 @@ class FunctionBasedViewIntegrationTests(TestCase):          request = factory.post('/', 'f00bar', content_type='application/json')          response = self.view(request)          expected = { -            'detail': 'JSON parse error - No JSON object could be decoded' +            'detail': JSON_ERROR          }          self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)          self.assertEqual(sanitise_json_error(response.data), expected) @@ -105,7 +111,7 @@ class FunctionBasedViewIntegrationTests(TestCase):          request = factory.post('/', form_data)          response = self.view(request)          expected = { -            'detail': 'JSON parse error - No JSON object could be decoded' +            'detail': JSON_ERROR          }          self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)          self.assertEqual(sanitise_json_error(response.data), expected)  | 
