aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/authtoken/models.py7
-rw-r--r--rest_framework/compat.py2
-rw-r--r--rest_framework/exceptions.py2
-rw-r--r--rest_framework/fields.py3
-rw-r--r--rest_framework/renderers.py3
-rw-r--r--rest_framework/serializers.py5
-rw-r--r--rest_framework/templatetags/rest_framework.py18
-rw-r--r--rest_framework/tests/test_fields.py9
-rw-r--r--rest_framework/tests/test_serializer.py29
-rw-r--r--rest_framework/tests/test_templatetags.py34
-rw-r--r--rest_framework/tests/test_testing.py9
-rw-r--r--rest_framework/views.py9
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):
"""