diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/authtoken/models.py | 7 | ||||
| -rw-r--r-- | rest_framework/compat.py | 2 | ||||
| -rw-r--r-- | rest_framework/exceptions.py | 2 | ||||
| -rw-r--r-- | rest_framework/fields.py | 3 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 3 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 5 | ||||
| -rw-r--r-- | rest_framework/templatetags/rest_framework.py | 18 | ||||
| -rw-r--r-- | rest_framework/tests/test_fields.py | 9 | ||||
| -rw-r--r-- | rest_framework/tests/test_serializer.py | 29 | ||||
| -rw-r--r-- | rest_framework/tests/test_templatetags.py | 34 | ||||
| -rw-r--r-- | rest_framework/tests/test_testing.py | 9 | ||||
| -rw-r--r-- | rest_framework/views.py | 9 | 
12 files changed, 103 insertions, 27 deletions
| diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 024f62bf..8eac2cc4 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -1,5 +1,5 @@ -import uuid -import hmac +import binascii +import os  from hashlib import sha1  from django.conf import settings  from django.db import models @@ -34,8 +34,7 @@ class Token(models.Model):          return super(Token, self).save(*args, **kwargs)      def generate_key(self): -        unique = uuid.uuid4() -        return hmac.new(unique.bytes, digestmod=sha1).hexdigest() +        return binascii.hexlify(os.urandom(20))      def __unicode__(self):          return self.key diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 36f5653a..3089b7fb 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -457,7 +457,7 @@ from django.test.client import RequestFactory as DjangoRequestFactory  from django.test.client import FakePayload  try:      # In 1.5 the test client uses force_bytes -    from django.utils.encoding import force_bytes_or_smart_bytes +    from django.utils.encoding import force_bytes as force_bytes_or_smart_bytes  except ImportError:      # In 1.3 and 1.4 the test client just uses smart_str      from django.utils.encoding import smart_str as force_bytes_or_smart_bytes diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 4276625a..0ac5866e 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -12,7 +12,7 @@ import math  class APIException(Exception):      """      Base class for REST framework exceptions. -    Subclasses should provide `.status_code` and `.detail` properties. +    Subclasses should provide `.status_code` and `.default_detail` properties.      """      status_code = status.HTTP_500_INTERNAL_SERVER_ERROR      default_detail = '' diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 2f475d6e..05daaab7 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -477,7 +477,8 @@ class URLField(CharField):      type_label = 'url'      def __init__(self, **kwargs): -        kwargs['validators'] = [validators.URLValidator()] +        if not 'validators' in kwargs: +            kwargs['validators'] = [validators.URLValidator()]          super(URLField, self).__init__(**kwargs) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 2fdd3337..e8afc26d 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -10,6 +10,7 @@ from __future__ import unicode_literals  import copy  import json +import django  from django import forms  from django.core.exceptions import ImproperlyConfigured  from django.http.multipartparser import parse_header @@ -597,7 +598,7 @@ class MultiPartRenderer(BaseRenderer):      media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'      format = 'multipart'      charset = 'utf-8' -    BOUNDARY = 'BoUnDaRyStRiNg' +    BOUNDARY = 'BoUnDaRyStRiNg' if django.VERSION >= (1, 5) else b'BoUnDaRyStRiNg'      def render(self, data, accepted_media_type=None, renderer_context=None):          return encode_multipart(self.BOUNDARY, data) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 536b040b..10256d47 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -501,7 +501,7 @@ class BaseSerializer(WritableField):              else:                  many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type))                  if many: -                    warnings.warn('Implict list/queryset serialization is deprecated. ' +                    warnings.warn('Implicit list/queryset serialization is deprecated. '                                    'Use the `many=True` flag when instantiating the serializer.',                                    DeprecationWarning, stacklevel=3) @@ -563,7 +563,7 @@ class BaseSerializer(WritableField):              else:                  many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict))                  if many: -                    warnings.warn('Implict list/queryset serialization is deprecated. ' +                    warnings.warn('Implicit list/queryset serialization is deprecated. '                                    'Use the `many=True` flag when instantiating the serializer.',                                    DeprecationWarning, stacklevel=2) @@ -893,6 +893,7 @@ class ModelSerializer(Serializer):              field_name = field.source or field_name              if field_name in exclusions \                  and not field.read_only \ +                and field.required \                  and not isinstance(field, Serializer):                  exclusions.remove(field_name)          return exclusions diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 83c046f9..beb8c5b0 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -6,7 +6,7 @@ from django.utils.encoding import iri_to_uri  from django.utils.html import escape  from django.utils.safestring import SafeData, mark_safe  from rest_framework.compat import urlparse, force_text, six, smart_urlquote -import re, string +import re  register = template.Library() @@ -189,6 +189,17 @@ simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net  simple_email_re = re.compile(r'^\S+@\S+\.\S+$') +def smart_urlquote_wrapper(matched_url): +    """ +    Simple wrapper for smart_urlquote. ValueError("Invalid IPv6 URL") can +    be raised here, see issue #1386 +    """ +    try: +        return smart_urlquote(matched_url) +    except ValueError: +        return None + +  @register.filter  def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True):      """ @@ -211,7 +222,6 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru      safe_input = isinstance(text, SafeData)      words = word_split_re.split(force_text(text))      for i, word in enumerate(words): -        match = None          if '.' in word or '@' in word or ':' in word:              # Deal with punctuation.              lead, middle, trail = '', word, '' @@ -233,9 +243,9 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru              url = None              nofollow_attr = ' rel="nofollow"' if nofollow else ''              if simple_url_re.match(middle): -                url = smart_urlquote(middle) +                url = smart_urlquote_wrapper(middle)              elif simple_url_2_re.match(middle): -                url = smart_urlquote('http://%s' % middle) +                url = smart_urlquote_wrapper('http://%s' % middle)              elif not ':' in middle and simple_email_re.match(middle):                  local, domain = middle.rsplit('@', 1)                  try: diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index 5c96bce9..e127feef 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -860,7 +860,9 @@ class SlugFieldTests(TestCase):  class URLFieldTests(TestCase):      """ -    Tests for URLField attribute values +    Tests for URLField attribute values. + +    (Includes test for #1210, checking that validators can be overridden.)      """      class URLFieldModel(RESTFrameworkModel): @@ -902,6 +904,11 @@ class URLFieldTests(TestCase):          self.assertEqual(getattr(serializer.fields['url_field'],                           'max_length'), 20) +    def test_validators_can_be_overridden(self): +        url_field = serializers.URLField(validators=[]) +        validators = url_field.validators +        self.assertEqual([], validators, 'Passing `validators` kwarg should have overridden default validators') +  class FieldMetadata(TestCase):      def setUp(self): diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index dbbb9a8f..47082190 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -91,6 +91,15 @@ class ActionItemSerializer(serializers.ModelSerializer):      class Meta:          model = ActionItem +class ActionItemSerializerOptionalFields(serializers.ModelSerializer): +    """ +    Intended to test that fields with `required=False` are excluded from validation. +    """ +    title = serializers.CharField(required=False) + +    class Meta: +        model = ActionItem +        fields = ('title',)  class ActionItemSerializerCustomRestore(serializers.ModelSerializer): @@ -308,7 +317,13 @@ class BasicTests(TestCase):          serializer.save()          self.assertIsNotNone(serializer.data.get('id',None), 'Model is saved. `id` should be set.') - +    def test_fields_marked_as_not_required_are_excluded_from_validation(self): +        """ +        Check that fields with `required=False` are included in list of exclusions. +        """ +        serializer = ActionItemSerializerOptionalFields(self.actionitem) +        exclusions = serializer.get_validation_exclusions() +        self.assertTrue('title' in exclusions, '`title` field was marked `required=False` and should be excluded')  class DictStyleSerializer(serializers.Serializer): @@ -1811,14 +1826,14 @@ class SerializerDefaultTrueBoolean(TestCase):          self.assertEqual(serializer.data['cat'], False)          self.assertEqual(serializer.data['dog'], False) -         +  class BoolenFieldTypeTest(TestCase):      '''      Ensure the various Boolean based model fields are rendered as the proper      field type -     +      ''' -     +      def setUp(self):          '''          Setup an ActionItemSerializer for BooleanTesting @@ -1834,11 +1849,11 @@ class BoolenFieldTypeTest(TestCase):          '''          bfield = self.serializer.get_fields()['done']          self.assertEqual(type(bfield), fields.BooleanField) -     +      def test_nullbooleanfield_type(self):          ''' -        Test that BooleanField is infered from models.NullBooleanField  -         +        Test that BooleanField is infered from models.NullBooleanField +          https://groups.google.com/forum/#!topic/django-rest-framework/D9mXEftpuQ8          '''          bfield = self.serializer.get_fields()['started'] diff --git a/rest_framework/tests/test_templatetags.py b/rest_framework/tests/test_templatetags.py index 609a9e08..d4da0c23 100644 --- a/rest_framework/tests/test_templatetags.py +++ b/rest_framework/tests/test_templatetags.py @@ -2,7 +2,7 @@  from __future__ import unicode_literals  from django.test import TestCase  from rest_framework.test import APIRequestFactory -from rest_framework.templatetags.rest_framework import add_query_param +from rest_framework.templatetags.rest_framework import add_query_param, urlize_quoted_links  factory = APIRequestFactory() @@ -17,3 +17,35 @@ class TemplateTagTests(TestCase):          json_url = add_query_param(request, "format", "json")          self.assertIn("q=%E6%9F%A5%E8%AF%A2", json_url)          self.assertIn("format=json", json_url) + + +class Issue1386Tests(TestCase): +    """ +    Covers #1386 +    """ + +    def test_issue_1386(self): +        """ +        Test function urlize_quoted_links with different args +        """ +        correct_urls = [ +            "asdf.com", +            "asdf.net", +            "www.as_df.org", +            "as.d8f.ghj8.gov", +        ] +        for i in correct_urls: +            res = urlize_quoted_links(i) +            self.assertNotEqual(res, i) +            self.assertIn(i, res) + +        incorrect_urls = [ +            "mailto://asdf@fdf.com", +            "asdf.netnet", +        ] +        for i in incorrect_urls: +            res = urlize_quoted_links(i) +            self.assertEqual(i, res) + +        # example from issue #1386, this shouldn't raise an exception +        _ = urlize_quoted_links("asdf:[/p]zxcv.com") diff --git a/rest_framework/tests/test_testing.py b/rest_framework/tests/test_testing.py index 48b8956b..71bd8b55 100644 --- a/rest_framework/tests/test_testing.py +++ b/rest_framework/tests/test_testing.py @@ -1,6 +1,8 @@  # -- coding: utf-8 --  from __future__ import unicode_literals +from io import BytesIO +  from django.contrib.auth.models import User  from django.test import TestCase  from rest_framework.compat import patterns, url @@ -143,3 +145,10 @@ class TestAPIRequestFactory(TestCase):          force_authenticate(request, user=user)          response = view(request)          self.assertEqual(response.data['user'], 'example') + +    def test_upload_file(self): +        # This is a 1x1 black png +        simple_png = BytesIO(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\x9cc````\x00\x00\x00\x05\x00\x01\xa5\xf6E@\x00\x00\x00\x00IEND\xaeB`\x82') +        simple_png.name = 'test.png' +        factory = APIRequestFactory() +        factory.post('/', data={'image': simple_png}) diff --git a/rest_framework/views.py b/rest_framework/views.py index e863af6d..02a6e25a 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -112,12 +112,13 @@ class APIView(View):      @property      def default_response_headers(self): -        # TODO: deprecate? -        # TODO: Only vary by accept if multiple renderers -        return { +        headers = {              'Allow': ', '.join(self.allowed_methods), -            'Vary': 'Accept'          } +        if len(self.renderer_classes) > 1: +            headers['Vary'] = 'Accept' +        return headers +      def http_method_not_allowed(self, request, *args, **kwargs):          """ | 
