aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/authentication.py12
-rw-r--r--rest_framework/authtoken/models.py4
-rw-r--r--rest_framework/compat.py20
-rw-r--r--rest_framework/fields.py59
-rw-r--r--rest_framework/mixins.py4
-rw-r--r--rest_framework/parsers.py24
-rw-r--r--rest_framework/relations.py24
-rw-r--r--rest_framework/renderers.py8
-rw-r--r--rest_framework/request.py9
-rw-r--r--rest_framework/response.py6
-rwxr-xr-xrest_framework/runtests/runtests.py2
-rw-r--r--rest_framework/serializers.py10
-rw-r--r--rest_framework/settings.py4
-rw-r--r--rest_framework/templatetags/rest_framework.py19
-rw-r--r--rest_framework/tests/authentication.py8
-rw-r--r--rest_framework/tests/files.py8
-rw-r--r--rest_framework/tests/genericrelations.py6
-rw-r--r--rest_framework/tests/generics.py10
-rw-r--r--rest_framework/tests/htmlrenderer.py10
-rw-r--r--rest_framework/tests/parsers.py2
-rw-r--r--rest_framework/tests/pk_relations.py208
-rw-r--r--rest_framework/tests/renderers.py18
-rw-r--r--rest_framework/tests/request.py12
-rw-r--r--rest_framework/tests/response.py9
-rw-r--r--rest_framework/tests/serializer.py26
-rw-r--r--rest_framework/tests/views.py10
-rw-r--r--rest_framework/utils/__init__.py15
-rw-r--r--rest_framework/utils/mediatypes.py2
28 files changed, 419 insertions, 130 deletions
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index 30c78ebc..15e531bf 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -3,7 +3,11 @@ Provides a set of pluggable authentication policies.
"""
from django.contrib.auth import authenticate
-from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError
+from django.utils.encoding import DjangoUnicodeDecodeError
+try:
+ from django.utils.encoding import smart_text
+except ImportError:
+ from django.utils.encoding import smart_unicode as smart_text
from rest_framework import exceptions
from rest_framework.compat import CsrfViewMiddleware
from rest_framework.authtoken.models import Token
@@ -36,13 +40,13 @@ class BasicAuthentication(BaseAuthentication):
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2 and auth[0].lower() == "basic":
try:
- auth_parts = base64.b64decode(auth[1]).partition(':')
+ auth_parts = base64.b64decode(auth[1].encode('iso-8859-1')).decode('iso-8859-1').partition(':')
except TypeError:
return None
try:
- userid = smart_unicode(auth_parts[0])
- password = smart_unicode(auth_parts[2])
+ userid = smart_text(auth_parts[0])
+ password = smart_text(auth_parts[2])
except DjangoUnicodeDecodeError:
return None
diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py
index 4da2aa62..7f5a75a3 100644
--- a/rest_framework/authtoken/models.py
+++ b/rest_framework/authtoken/models.py
@@ -19,8 +19,8 @@ class Token(models.Model):
return super(Token, self).save(*args, **kwargs)
def generate_key(self):
- unique = str(uuid.uuid4())
- return hmac.new(unique, digestmod=sha1).hexdigest()
+ unique = uuid.uuid4()
+ return hmac.new(unique.bytes, digestmod=sha1).hexdigest()
def __unicode__(self):
return self.key
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 86952fb8..d5a9d995 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -3,6 +3,9 @@ The `compat` module provides support for backwards compatibility with older
versions of django/python, and compatibility wrappers around optional packages.
"""
# flake8: noqa
+from __future__ import unicode_literals
+import six
+
import django
# location of patterns, url, include changes in 1.4 onwards
@@ -20,9 +23,11 @@ except:
# cStringIO only if it's available, otherwise StringIO
try:
- import cStringIO as StringIO
+ import cStringIO.StringIO as StringIO
except ImportError:
- import StringIO
+ from six import StringIO
+
+from six import BytesIO
# Try to import PIL in either of the two ways it can end up installed.
@@ -54,7 +59,7 @@ else:
try:
from django.contrib.auth.models import User
except ImportError:
- raise ImportError(u"User model is not to be found.")
+ raise ImportError("User model is not to be found.")
# First implementation of Django class-based views did not include head method
@@ -75,11 +80,11 @@ else:
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
- raise TypeError(u"You tried to pass in the %s method name as a "
- u"keyword argument to %s(). Don't do that."
+ raise TypeError("You tried to pass in the %s method name as a "
+ "keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
- raise TypeError(u"%s() received an invalid keyword %r" % (
+ raise TypeError("%s() received an invalid keyword %r" % (
cls.__name__, key))
def view(request, *args, **kwargs):
@@ -146,7 +151,8 @@ else:
randrange = random.SystemRandom().randrange
else:
randrange = random.randrange
- _MAX_CSRF_KEY = 18446744073709551616L # 2 << 63
+
+ _MAX_CSRF_KEY = 18446744073709551616 # 2 << 63
REASON_NO_REFERER = "Referer checking failed - no Referer."
REASON_BAD_REFERER = "Referer checking failed - %s does not match %s."
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index d8b82e5f..23e2ac44 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1,20 +1,31 @@
+
+from __future__ import unicode_literals
+import six
+
import copy
import datetime
import inspect
import re
import warnings
-from io import BytesIO
-
from django.core import validators
from django.core.exceptions import ValidationError
from django.conf import settings
from django import forms
from django.forms import widgets
-from django.utils.encoding import is_protected_type, smart_unicode
+from django.utils.encoding import is_protected_type
+try:
+ from django.utils.encoding import smart_text
+except ImportError:
+ from django.utils.encoding import smart_unicode as smart_text
from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import parse_date, parse_datetime
from rest_framework.compat import timezone
+from rest_framework.compat import BytesIO
+try:
+ from urllib.parse import urlparse
+except ImportError:
+ from urlparse import urlparse
def is_simple_callable(obj):
@@ -93,11 +104,11 @@ class Field(object):
if is_protected_type(value):
return value
- elif hasattr(value, '__iter__') and not isinstance(value, (dict, basestring)):
+ elif hasattr(value, '__iter__') and not isinstance(value, (dict, six.string_types)):
return [self.to_native(item) for item in value]
elif isinstance(value, dict):
return dict(map(self.to_native, (k, v)) for k, v in value.items())
- return smart_unicode(value)
+ return smart_text(value)
def attributes(self):
"""
@@ -256,7 +267,7 @@ class BooleanField(WritableField):
form_field_class = forms.BooleanField
widget = widgets.CheckboxInput
default_error_messages = {
- 'invalid': _(u"'%s' value must be either True or False."),
+ 'invalid': _("'%s' value must be either True or False."),
}
empty = False
@@ -296,9 +307,9 @@ class CharField(WritableField):
super(CharField, self).validate(value)
def from_native(self, value):
- if isinstance(value, basestring) or value is None:
+ if isinstance(value, six.string_types) or value is None:
return value
- return smart_unicode(value)
+ return smart_text(value)
class URLField(CharField):
@@ -357,10 +368,10 @@ class ChoiceField(WritableField):
if isinstance(v, (list, tuple)):
# This is an optgroup, so look inside the group for options
for k2, v2 in v:
- if value == smart_unicode(k2):
+ if value == smart_text(k2):
return True
else:
- if value == smart_unicode(k) or value == k:
+ if value == smart_text(k) or value == k:
return True
return False
@@ -400,7 +411,7 @@ class RegexField(CharField):
return self._regex
def _set_regex(self, regex):
- if isinstance(regex, basestring):
+ if isinstance(regex, six.string_types):
regex = re.compile(regex)
self._regex = regex
if hasattr(self, '_regex_validator') and self._regex_validator in self.validators:
@@ -423,10 +434,10 @@ class DateField(WritableField):
form_field_class = forms.DateField
default_error_messages = {
- 'invalid': _(u"'%s' value has an invalid date format. It must be "
- u"in YYYY-MM-DD format."),
- 'invalid_date': _(u"'%s' value has the correct format (YYYY-MM-DD) "
- u"but it is an invalid date."),
+ 'invalid': _("'%s' value has an invalid date format. It must be "
+ "in YYYY-MM-DD format."),
+ 'invalid_date': _("'%s' value has the correct format (YYYY-MM-DD) "
+ "but it is an invalid date."),
}
empty = None
@@ -462,13 +473,13 @@ class DateTimeField(WritableField):
form_field_class = forms.DateTimeField
default_error_messages = {
- 'invalid': _(u"'%s' value has an invalid format. It must be in "
- u"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."),
- 'invalid_date': _(u"'%s' value has the correct format "
- u"(YYYY-MM-DD) but it is an invalid date."),
- 'invalid_datetime': _(u"'%s' value has the correct format "
- u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) "
- u"but it is an invalid date/time."),
+ 'invalid': _("'%s' value has an invalid format. It must be in "
+ "YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."),
+ 'invalid_date': _("'%s' value has the correct format "
+ "(YYYY-MM-DD) but it is an invalid date."),
+ 'invalid_datetime': _("'%s' value has the correct format "
+ "(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) "
+ "but it is an invalid date/time."),
}
empty = None
@@ -485,8 +496,8 @@ class DateTimeField(WritableField):
# local time. This won't work during DST change, but we can't
# do much about it, so we let the exceptions percolate up the
# call stack.
- warnings.warn(u"DateTimeField received a naive datetime (%s)"
- u" while time zone support is active." % value,
+ warnings.warn("DateTimeField received a naive datetime (%s)"
+ " while time zone support is active." % value,
RuntimeWarning)
default_timezone = timezone.get_default_timezone()
value = timezone.make_aware(value, default_timezone)
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 2700606d..503376ce 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -4,6 +4,8 @@ Basic building blocks for generic class based views.
We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
+from __future__ import unicode_literals
+
from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
@@ -38,7 +40,7 @@ class ListModelMixin(object):
List a queryset.
Should be mixed in with `MultipleObjectAPIView`.
"""
- empty_error = u"Empty list and '%(class_name)s.allow_empty' is False."
+ empty_error = "Empty list and '%(class_name)s.allow_empty' is False."
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index 4841676c..d5cfaaf8 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -5,6 +5,8 @@ They give us a generic way of being able to handle various media types
on the request, such as form content or json encoded data.
"""
+import six
+
from django.http import QueryDict
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError
@@ -55,9 +57,10 @@ class JSONParser(BaseParser):
`files` will always be `None`.
"""
try:
- return json.load(stream)
- except ValueError, exc:
- raise ParseError('JSON parse error - %s' % unicode(exc))
+ data = stream.read().decode('iso-8859-1')
+ return json.loads(data)
+ except ValueError as exc:
+ raise ParseError('JSON parse error - %s' % six.text_type(exc))
class YAMLParser(BaseParser):
@@ -75,9 +78,10 @@ class YAMLParser(BaseParser):
`files` will always be `None`.
"""
try:
- return yaml.safe_load(stream)
- except (ValueError, yaml.parser.ParserError), exc:
- raise ParseError('YAML parse error - %s' % unicode(exc))
+ data = stream.read().decode('iso-8859-1')
+ return yaml.safe_load(data)
+ except (ValueError, yaml.parser.ParserError) as exc:
+ raise ParseError('YAML parse error - %s' % six.u(exc))
class FormParser(BaseParser):
@@ -121,8 +125,8 @@ class MultiPartParser(BaseParser):
parser = DjangoMultiPartParser(meta, stream, upload_handlers)
data, files = parser.parse()
return DataAndFiles(data, files)
- except MultiPartParserError, exc:
- raise ParseError('Multipart form parse error - %s' % unicode(exc))
+ except MultiPartParserError as exc:
+ raise ParseError('Multipart form parse error - %s' % six.u(exc))
class XMLParser(BaseParser):
@@ -135,8 +139,8 @@ class XMLParser(BaseParser):
def parse(self, stream, media_type=None, parser_context=None):
try:
tree = ET.parse(stream)
- except (ExpatError, ETParseError, ValueError), exc:
- raise ParseError('XML parse error - %s' % unicode(exc))
+ except (ExpatError, ETParseError, ValueError) as exc:
+ raise ParseError('XML parse error - %s' % six.u(exc))
data = self._xml_convert(tree.getroot())
return data
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 9b3a7790..fe8cbc44 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,9 +1,15 @@
+
+from __future__ import unicode_literals
+
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.urlresolvers import resolve, get_script_prefix
from django import forms
from django.forms import widgets
from django.forms.models import ModelChoiceIterator
-from django.utils.encoding import smart_unicode
+try:
+ from django.utils.encoding import smart_text
+except ImportError:
+ from django.utils.encoding import smart_unicode as smart_text
from rest_framework.fields import Field, WritableField
from rest_framework.reverse import reverse
from urlparse import urlparse
@@ -59,8 +65,8 @@ class RelatedField(WritableField):
"""
Return a readable representation for use with eg. select widgets.
"""
- desc = smart_unicode(obj)
- ident = smart_unicode(self.to_native(obj))
+ desc = smart_text(obj)
+ ident = smart_text(self.to_native(obj))
if desc == ident:
return desc
return "%s - %s" % (desc, ident)
@@ -176,8 +182,8 @@ class PrimaryKeyRelatedField(RelatedField):
"""
Return a readable representation for use with eg. select widgets.
"""
- desc = smart_unicode(obj)
- ident = smart_unicode(self.to_native(obj.pk))
+ desc = smart_text(obj)
+ ident = smart_text(self.to_native(obj.pk))
if desc == ident:
return desc
return "%s - %s" % (desc, ident)
@@ -193,7 +199,7 @@ class PrimaryKeyRelatedField(RelatedField):
try:
return self.queryset.get(pk=data)
except ObjectDoesNotExist:
- msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data)
+ msg = "Invalid pk '%s' - object does not exist." % smart_text(data)
raise ValidationError(msg)
def field_to_native(self, obj, field_name):
@@ -222,8 +228,8 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
"""
Return a readable representation for use with eg. select widgets.
"""
- desc = smart_unicode(obj)
- ident = smart_unicode(self.to_native(obj.pk))
+ desc = smart_text(obj)
+ ident = smart_text(self.to_native(obj.pk))
if desc == ident:
return desc
return "%s - %s" % (desc, ident)
@@ -249,7 +255,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
try:
return self.queryset.get(pk=data)
except ObjectDoesNotExist:
- msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data)
+ msg = "Invalid pk '%s' - object does not exist." % smart_text(data)
raise ValidationError(msg)
### Slug relationships
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index a4ae717d..54930167 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -6,6 +6,8 @@ on the response, such as JSON encoded data or HTML output.
REST framework also provides an HTML renderer the renders the browsable API.
"""
+from __future__ import unicode_literals
+
import copy
import string
from django import forms
@@ -60,7 +62,7 @@ class JSONRenderer(BaseRenderer):
if accepted_media_type:
# If the media type looks like 'application/json; indent=4',
# then pretty print the result.
- base_media_type, params = parse_header(accepted_media_type)
+ base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
indent = params.get('indent', indent)
try:
indent = max(min(int(indent), 8), 0)
@@ -100,7 +102,7 @@ class JSONPRenderer(JSONRenderer):
callback = self.get_callback(renderer_context)
json = super(JSONPRenderer, self).render(data, accepted_media_type,
renderer_context)
- return u"%s(%s);" % (callback, json)
+ return "%s(%s);" % (callback, json)
class XMLRenderer(BaseRenderer):
@@ -357,7 +359,7 @@ class BrowsableAPIRenderer(BaseRenderer):
# Creating an on the fly form see:
# http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
- OnTheFlyForm = type("OnTheFlyForm", (forms.Form,), fields)
+ OnTheFlyForm = type(str("OnTheFlyForm"), (forms.Form,), fields)
data = (obj is not None) and serializer.data or None
form_instance = OnTheFlyForm(data)
return form_instance
diff --git a/rest_framework/request.py b/rest_framework/request.py
index b7133608..c50ae5ad 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -9,7 +9,8 @@ The wrapped request then offers a richer API, in particular :
- full support of PUT method, including support for file uploads
- form overloading of HTTP method, content type and content
"""
-from StringIO import StringIO
+import six
+from rest_framework.compat import BytesIO
from django.http.multipartparser import parse_header
from rest_framework import exceptions
@@ -20,7 +21,7 @@ def is_form_media_type(media_type):
"""
Return True if the media type is a valid form media type.
"""
- base_media_type, params = parse_header(media_type)
+ base_media_type, params = parse_header(media_type.encode('iso-8859-1'))
return (base_media_type == 'application/x-www-form-urlencoded' or
base_media_type == 'multipart/form-data')
@@ -233,7 +234,7 @@ class Request(object):
elif hasattr(self._request, 'read'):
self._stream = self._request
else:
- self._stream = StringIO(self.raw_post_data)
+ self._stream = BytesIO(self.raw_post_data)
def _perform_form_overloading(self):
"""
@@ -268,7 +269,7 @@ class Request(object):
self._CONTENT_PARAM in self._data and
self._CONTENTTYPE_PARAM in self._data):
self._content_type = self._data[self._CONTENTTYPE_PARAM]
- self._stream = StringIO(self._data[self._CONTENT_PARAM])
+ self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode('iso-8859-1'))
self._data, self._files = (Empty, Empty)
def _parse(self):
diff --git a/rest_framework/response.py b/rest_framework/response.py
index be78c43a..cad95611 100644
--- a/rest_framework/response.py
+++ b/rest_framework/response.py
@@ -1,3 +1,5 @@
+import six
+
from django.core.handlers.wsgi import STATUS_CODE_TEXT
from django.template.response import SimpleTemplateResponse
@@ -22,9 +24,9 @@ class Response(SimpleTemplateResponse):
self.data = data
self.template_name = template_name
self.exception = exception
-
+
if headers:
- for name,value in headers.iteritems():
+ for name, value in six.iteritems(headers):
self[name] = value
@property
diff --git a/rest_framework/runtests/runtests.py b/rest_framework/runtests/runtests.py
index 505994e2..4a333fb3 100755
--- a/rest_framework/runtests/runtests.py
+++ b/rest_framework/runtests/runtests.py
@@ -33,7 +33,7 @@ def main():
elif len(sys.argv) == 1:
test_case = ''
else:
- print usage()
+ print(usage())
sys.exit(1)
failures = test_runner.run_tests(['tests' + test_case])
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 24674f2a..91fbe176 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1,3 +1,5 @@
+import six
+
import copy
import datetime
import types
@@ -63,7 +65,7 @@ def _get_declared_fields(bases, attrs):
Note that all fields from the base classes are used.
"""
fields = [(field_name, attrs.pop(field_name))
- for field_name, obj in attrs.items()
+ for field_name, obj in list(six.iteritems(attrs))
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1].creation_counter)
@@ -72,7 +74,7 @@ def _get_declared_fields(bases, attrs):
# in order to maintain the correct order of fields.
for base in bases[::-1]:
if hasattr(base, 'base_fields'):
- fields = base.base_fields.items() + fields
+ fields = list(base.base_fields.items()) + fields
return SortedDict(fields)
@@ -341,8 +343,8 @@ class BaseSerializer(Field):
return self.object
-class Serializer(BaseSerializer):
- __metaclass__ = SerializerMetaclass
+class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)):
+ pass
class ModelSerializerOptions(SerializerOptions):
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index ee24a4ad..9e73bbfb 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -19,6 +19,8 @@ back to the defaults.
"""
from django.conf import settings
from django.utils import importlib
+from six import string_types
+
USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None)
@@ -98,7 +100,7 @@ def perform_import(val, setting_name):
If the given setting is a string import notation,
then perform the necessary import or imports.
"""
- if isinstance(val, basestring):
+ if isinstance(val, string_types):
return import_from_string(val, setting_name)
elif isinstance(val, (list, tuple)):
return [import_from_string(item, setting_name) for item in val]
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index 09c658bc..18427802 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -1,10 +1,19 @@
+from __future__ import unicode_literals
+import six
+
from django import template
from django.core.urlresolvers import reverse
from django.http import QueryDict
-from django.utils.encoding import force_unicode
+try:
+ from django.utils.encoding import force_text
+except ImportError:
+ from django.utils.encoding import force_unicode as force_text
from django.utils.html import escape
from django.utils.safestring import SafeData, mark_safe
-from urlparse import urlsplit, urlunsplit
+try:
+ from urllib.parse import urlsplit, urlunsplit
+except ImportError:
+ from urlparse import urlsplit, urlunsplit
import re
import string
@@ -179,7 +188,7 @@ def add_class(value, css_class):
In the case of REST Framework, the filter is used to add Bootstrap-specific
classes to the forms.
"""
- html = unicode(value)
+ html = six.text_type(value)
match = class_re.search(html)
if match:
m = re.search(r'^%s$|^%s\s|\s%s\s|\s%s$' % (css_class, css_class,
@@ -213,7 +222,7 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
"""
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
safe_input = isinstance(text, SafeData)
- words = word_split_re.split(force_unicode(text))
+ words = word_split_re.split(force_text(text))
nofollow_attr = nofollow and ' rel="nofollow"' or ''
for i, word in enumerate(words):
match = None
@@ -249,4 +258,4 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
words[i] = mark_safe(word)
elif autoescape:
words[i] = escape(word)
- return mark_safe(u''.join(words))
+ return mark_safe(''.join(words))
diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py
index 838e081b..8c0bfc47 100644
--- a/rest_framework/tests/authentication.py
+++ b/rest_framework/tests/authentication.py
@@ -42,13 +42,13 @@ class BasicAuthTests(TestCase):
def test_post_form_passing_basic_auth(self):
"""Ensure POSTing json over basic auth with correct credentials passes and does not require CSRF"""
- auth = 'Basic %s' % base64.encodestring('%s:%s' % (self.username, self.password)).strip()
+ auth = 'Basic ' + base64.encodestring(('%s:%s' % (self.username, self.password)).encode('iso-8859-1')).strip().decode('iso-8859-1')
response = self.csrf_client.post('/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
self.assertEqual(response.status_code, 200)
def test_post_json_passing_basic_auth(self):
"""Ensure POSTing form over basic auth with correct credentials passes and does not require CSRF"""
- auth = 'Basic %s' % base64.encodestring('%s:%s' % (self.username, self.password)).strip()
+ auth = 'Basic ' + base64.encodestring(('%s:%s' % (self.username, self.password)).encode('iso-8859-1')).strip().decode('iso-8859-1')
response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json', HTTP_AUTHORIZATION=auth)
self.assertEqual(response.status_code, 200)
@@ -158,7 +158,7 @@ class TokenAuthTests(TestCase):
response = client.post('/auth-token/',
json.dumps({'username': self.username, 'password': self.password}), 'application/json')
self.assertEqual(response.status_code, 200)
- self.assertEqual(json.loads(response.content)['token'], self.key)
+ self.assertEqual(json.loads(response.content.decode('ascii'))['token'], self.key)
def test_token_login_json_bad_creds(self):
"""Ensure token login view using JSON POST fails if bad credentials are used."""
@@ -180,4 +180,4 @@ class TokenAuthTests(TestCase):
response = client.post('/auth-token/',
{'username': self.username, 'password': self.password})
self.assertEqual(response.status_code, 200)
- self.assertEqual(json.loads(response.content)['token'], self.key)
+ self.assertEqual(json.loads(response.content.decode('ascii'))['token'], self.key)
diff --git a/rest_framework/tests/files.py b/rest_framework/tests/files.py
index 5dd57b7c..42e8ed5f 100644
--- a/rest_framework/tests/files.py
+++ b/rest_framework/tests/files.py
@@ -1,5 +1,7 @@
-import StringIO
+from rest_framework.compat import BytesIO
+
import datetime
+import six
from django.test import TestCase
@@ -28,9 +30,9 @@ class FileSerializerTests(TestCase):
def test_create(self):
now = datetime.datetime.now()
- file = StringIO.StringIO('stuff')
+ file = BytesIO(six.b('stuff'))
file.name = 'stuff.txt'
- file.size = file.len
+ file.size = len(file.getvalue())
serializer = UploadedFileSerializer(data={'created': now}, files={'file': file})
uploaded_file = UploadedFile(file=file, created=now)
self.assertTrue(serializer.is_valid())
diff --git a/rest_framework/tests/genericrelations.py b/rest_framework/tests/genericrelations.py
index bc7378e1..ba29dbed 100644
--- a/rest_framework/tests/genericrelations.py
+++ b/rest_framework/tests/genericrelations.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from django.test import TestCase
from rest_framework import serializers
from rest_framework.tests.models import *
@@ -27,7 +29,7 @@ class TestGenericRelations(TestCase):
serializer = BookmarkSerializer(self.bookmark)
expected = {
- 'tags': [u'django', u'python'],
- 'url': u'https://www.djangoproject.com/'
+ 'tags': ['django', 'python'],
+ 'url': 'https://www.djangoproject.com/'
}
self.assertEquals(serializer.data, expected)
diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py
index 7c24d84e..a877574e 100644
--- a/rest_framework/tests/generics.py
+++ b/rest_framework/tests/generics.py
@@ -1,3 +1,7 @@
+from __future__ import unicode_literals
+
+import six
+
from django.db import models
from django.test import TestCase
from django.test.client import RequestFactory
@@ -72,7 +76,7 @@ class TestRootView(TestCase):
content_type='application/json')
response = self.view(request).render()
self.assertEquals(response.status_code, status.HTTP_201_CREATED)
- self.assertEquals(response.data, {'id': 4, 'text': u'foobar'})
+ self.assertEquals(response.data, {'id': 4, 'text': 'foobar'})
created = self.objects.get(id=4)
self.assertEquals(created.text, 'foobar')
@@ -127,7 +131,7 @@ class TestRootView(TestCase):
content_type='application/json')
response = self.view(request).render()
self.assertEquals(response.status_code, status.HTTP_201_CREATED)
- self.assertEquals(response.data, {'id': 4, 'text': u'foobar'})
+ self.assertEquals(response.data, {'id': 4, 'text': 'foobar'})
created = self.objects.get(id=4)
self.assertEquals(created.text, 'foobar')
@@ -188,7 +192,7 @@ class TestInstanceView(TestCase):
request = factory.delete('/1')
response = self.view(request, pk=1).render()
self.assertEquals(response.status_code, status.HTTP_204_NO_CONTENT)
- self.assertEquals(response.content, '')
+ self.assertEquals(response.content, six.b(''))
ids = [obj.id for obj in self.objects.all()]
self.assertEquals(ids, [2, 3])
diff --git a/rest_framework/tests/htmlrenderer.py b/rest_framework/tests/htmlrenderer.py
index 54096206..d4662465 100644
--- a/rest_framework/tests/htmlrenderer.py
+++ b/rest_framework/tests/htmlrenderer.py
@@ -1,3 +1,5 @@
+import six
+
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.test import TestCase
@@ -68,13 +70,13 @@ class TemplateHTMLRendererTests(TestCase):
def test_not_found_html_view(self):
response = self.client.get('/not_found')
self.assertEquals(response.status_code, 404)
- self.assertEquals(response.content, "404 Not Found")
+ self.assertEquals(response.content, six.b("404 Not Found"))
self.assertEquals(response['Content-Type'], 'text/html')
def test_permission_denied_html_view(self):
response = self.client.get('/permission_denied')
self.assertEquals(response.status_code, 403)
- self.assertEquals(response.content, "403 Forbidden")
+ self.assertEquals(response.content, six.b("403 Forbidden"))
self.assertEquals(response['Content-Type'], 'text/html')
@@ -105,11 +107,11 @@ class TemplateHTMLRendererExceptionTests(TestCase):
def test_not_found_html_view_with_template(self):
response = self.client.get('/not_found')
self.assertEquals(response.status_code, 404)
- self.assertEquals(response.content, "404: Not found")
+ self.assertEquals(response.content, six.b("404: Not found"))
self.assertEquals(response['Content-Type'], 'text/html')
def test_permission_denied_html_view_with_template(self):
response = self.client.get('/permission_denied')
self.assertEquals(response.status_code, 403)
- self.assertEquals(response.content, "403: Permission denied")
+ self.assertEquals(response.content, six.b("403: Permission denied"))
self.assertEquals(response['Content-Type'], 'text/html')
diff --git a/rest_framework/tests/parsers.py b/rest_framework/tests/parsers.py
index 8ab8a52f..ffa39b1f 100644
--- a/rest_framework/tests/parsers.py
+++ b/rest_framework/tests/parsers.py
@@ -131,7 +131,7 @@
# self.assertEqual(data['key1'], 'val1')
# self.assertEqual(files['file1'].read(), 'blablabla')
-from StringIO import StringIO
+from rest_framework.compat import StringIO
from django import forms
from django.test import TestCase
from rest_framework.parsers import FormParser
diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py
new file mode 100644
index 00000000..cbafa3e0
--- /dev/null
+++ b/rest_framework/tests/pk_relations.py
@@ -0,0 +1,208 @@
+from __future__ import unicode_literals
+
+from django.db import models
+from django.test import TestCase
+from rest_framework import serializers
+
+
+# ManyToMany
+
+class ManyToManyTarget(models.Model):
+ name = models.CharField(max_length=100)
+
+
+class ManyToManySource(models.Model):
+ name = models.CharField(max_length=100)
+ targets = models.ManyToManyField(ManyToManyTarget, related_name='sources')
+
+
+class ManyToManyTargetSerializer(serializers.ModelSerializer):
+ sources = serializers.ManyPrimaryKeyRelatedField()
+
+ class Meta:
+ model = ManyToManyTarget
+
+
+class ManyToManySourceSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ManyToManySource
+
+
+# ForeignKey
+
+class ForeignKeyTarget(models.Model):
+ name = models.CharField(max_length=100)
+
+
+class ForeignKeySource(models.Model):
+ name = models.CharField(max_length=100)
+ target = models.ForeignKey(ForeignKeyTarget, related_name='sources')
+
+
+class ForeignKeyTargetSerializer(serializers.ModelSerializer):
+ sources = serializers.ManyPrimaryKeyRelatedField(read_only=True)
+
+ class Meta:
+ model = ForeignKeyTarget
+
+
+class ForeignKeySourceSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ForeignKeySource
+
+
+# TODO: Add test that .data cannot be accessed prior to .is_valid
+
+class PrimaryKeyManyToManyTests(TestCase):
+ def setUp(self):
+ for idx in range(1, 4):
+ target = ManyToManyTarget(name='target-%d' % idx)
+ target.save()
+ source = ManyToManySource(name='source-%d' % idx)
+ source.save()
+ for target in ManyToManyTarget.objects.all():
+ source.targets.add(target)
+
+ def test_many_to_many_retrieve(self):
+ queryset = ManyToManySource.objects.all()
+ serializer = ManyToManySourceSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': 'source-1', 'targets': [1]},
+ {'id': 2, 'name': 'source-2', 'targets': [1, 2]},
+ {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]}
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_reverse_many_to_many_retrieve(self):
+ queryset = ManyToManyTarget.objects.all()
+ serializer = ManyToManyTargetSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]},
+ {'id': 2, 'name': 'target-2', 'sources': [2, 3]},
+ {'id': 3, 'name': 'target-3', 'sources': [3]}
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_many_to_many_update(self):
+ data = {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]}
+ instance = ManyToManySource.objects.get(pk=1)
+ serializer = ManyToManySourceSerializer(instance, data=data)
+ self.assertTrue(serializer.is_valid())
+ self.assertEquals(serializer.data, data)
+ serializer.save()
+
+ # Ensure source 1 is updated, and everything else is as expected
+ queryset = ManyToManySource.objects.all()
+ serializer = ManyToManySourceSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]},
+ {'id': 2, 'name': 'source-2', 'targets': [1, 2]},
+ {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]}
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_reverse_many_to_many_update(self):
+ data = {'id': 1, 'name': 'target-1', 'sources': [1]}
+ instance = ManyToManyTarget.objects.get(pk=1)
+ serializer = ManyToManyTargetSerializer(instance, data=data)
+ self.assertTrue(serializer.is_valid())
+ self.assertEquals(serializer.data, data)
+ serializer.save()
+
+ # Ensure target 1 is updated, and everything else is as expected
+ queryset = ManyToManyTarget.objects.all()
+ serializer = ManyToManyTargetSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': 'target-1', 'sources': [1]},
+ {'id': 2, 'name': 'target-2', 'sources': [2, 3]},
+ {'id': 3, 'name': 'target-3', 'sources': [3]}
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_reverse_many_to_many_create(self):
+ data = {'id': 4, 'name': 'target-4', 'sources': [1, 3]}
+ serializer = ManyToManyTargetSerializer(data=data)
+ self.assertTrue(serializer.is_valid())
+ obj = serializer.save()
+ self.assertEquals(serializer.data, data)
+ self.assertEqual(obj.name, 'target-4')
+
+ # Ensure target 4 is added, and everything else is as expected
+ queryset = ManyToManyTarget.objects.all()
+ serializer = ManyToManyTargetSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]},
+ {'id': 2, 'name': 'target-2', 'sources': [2, 3]},
+ {'id': 3, 'name': 'target-3', 'sources': [3]},
+ {'id': 4, 'name': 'target-4', 'sources': [1, 3]}
+ ]
+ self.assertEquals(serializer.data, expected)
+
+
+class PrimaryKeyForeignKeyTests(TestCase):
+ def setUp(self):
+ target = ForeignKeyTarget(name='target-1')
+ target.save()
+ new_target = ForeignKeyTarget(name='target-2')
+ new_target.save()
+ for idx in range(1, 4):
+ source = ForeignKeySource(name='source-%d' % idx, target=target)
+ source.save()
+
+ def test_foreign_key_retrieve(self):
+ queryset = ForeignKeySource.objects.all()
+ serializer = ForeignKeySourceSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': 'source-1', 'target': 1},
+ {'id': 2, 'name': 'source-2', 'target': 1},
+ {'id': 3, 'name': 'source-3', 'target': 1}
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_reverse_foreign_key_retrieve(self):
+ queryset = ForeignKeyTarget.objects.all()
+ serializer = ForeignKeyTargetSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]},
+ {'id': 2, 'name': 'target-2', 'sources': []},
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_foreign_key_update(self):
+ data = {'id': 1, 'name': 'source-1', 'target': 2}
+ instance = ForeignKeySource.objects.get(pk=1)
+ serializer = ForeignKeySourceSerializer(instance, data=data)
+ self.assertTrue(serializer.is_valid())
+ self.assertEquals(serializer.data, data)
+ serializer.save()
+
+ # # Ensure source 1 is updated, and everything else is as expected
+ queryset = ForeignKeySource.objects.all()
+ serializer = ForeignKeySourceSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': 'source-1', 'target': 2},
+ {'id': 2, 'name': 'source-2', 'target': 1},
+ {'id': 3, 'name': 'source-3', 'target': 1}
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ # reverse foreign keys MUST be read_only
+ # In the general case they do not provide .remove() or .clear()
+ # and cannot be arbitrarily set.
+
+ # def test_reverse_foreign_key_update(self):
+ # data = {'id': 1, 'name': 'target-1', 'sources': [1]}
+ # instance = ForeignKeyTarget.objects.get(pk=1)
+ # serializer = ForeignKeyTargetSerializer(instance, data=data)
+ # self.assertTrue(serializer.is_valid())
+ # self.assertEquals(serializer.data, data)
+ # serializer.save()
+
+ # # Ensure target 1 is updated, and everything else is as expected
+ # queryset = ForeignKeyTarget.objects.all()
+ # serializer = ForeignKeyTargetSerializer(queryset)
+ # expected = [
+ # {'id': 1, 'name': 'target-1', 'sources': [1]},
+ # {'id': 2, 'name': 'target-2', 'sources': []},
+ # ]
+ # self.assertEquals(serializer.data, expected)
diff --git a/rest_framework/tests/renderers.py b/rest_framework/tests/renderers.py
index c1b4e624..b02fccf4 100644
--- a/rest_framework/tests/renderers.py
+++ b/rest_framework/tests/renderers.py
@@ -1,5 +1,6 @@
import pickle
import re
+import six
from django.core.cache import cache
from django.test import TestCase
@@ -14,7 +15,7 @@ from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
from rest_framework.parsers import YAMLParser, XMLParser
from rest_framework.settings import api_settings
-from StringIO import StringIO
+from rest_framework.compat import StringIO
import datetime
from decimal import Decimal
@@ -22,8 +23,8 @@ from decimal import Decimal
DUMMYSTATUS = status.HTTP_200_OK
DUMMYCONTENT = 'dummycontent'
-RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
-RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
+RENDERER_A_SERIALIZER = lambda x: ('Renderer A: %s' % x).encode('ascii')
+RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii')
expected_results = [
@@ -140,7 +141,7 @@ class RendererEndToEndTests(TestCase):
resp = self.client.head('/')
self.assertEquals(resp.status_code, DUMMYSTATUS)
self.assertEquals(resp['Content-Type'], RendererA.media_type)
- self.assertEquals(resp.content, '')
+ self.assertEquals(resp.content, six.b(''))
def test_default_renderer_serializes_content_on_accept_any(self):
"""If the Accept header is set to */* the default renderer should serialize the response."""
@@ -267,7 +268,8 @@ class JSONPRendererTests(TestCase):
HTTP_ACCEPT='application/javascript')
self.assertEquals(resp.status_code, 200)
self.assertEquals(resp['Content-Type'], 'application/javascript')
- self.assertEquals(resp.content, 'callback(%s);' % _flat_repr)
+ self.assertEquals(resp.content,
+ ('callback(%s);' % _flat_repr).encode('ascii'))
def test_without_callback_without_json_renderer(self):
"""
@@ -277,7 +279,8 @@ class JSONPRendererTests(TestCase):
HTTP_ACCEPT='application/javascript')
self.assertEquals(resp.status_code, 200)
self.assertEquals(resp['Content-Type'], 'application/javascript')
- self.assertEquals(resp.content, 'callback(%s);' % _flat_repr)
+ self.assertEquals(resp.content,
+ ('callback(%s);' % _flat_repr).encode('ascii'))
def test_with_callback(self):
"""
@@ -288,7 +291,8 @@ class JSONPRendererTests(TestCase):
HTTP_ACCEPT='application/javascript')
self.assertEquals(resp.status_code, 200)
self.assertEquals(resp['Content-Type'], 'application/javascript')
- self.assertEquals(resp.content, '%s(%s);' % (callback_func, _flat_repr))
+ self.assertEquals(resp.content,
+ ('%s(%s);' % (callback_func, _flat_repr)).encode('ascii'))
if yaml:
diff --git a/rest_framework/tests/request.py b/rest_framework/tests/request.py
index 1f05ff8f..fe5116a8 100644
--- a/rest_framework/tests/request.py
+++ b/rest_framework/tests/request.py
@@ -1,6 +1,8 @@
"""
Tests for content parsing, and form-overloaded content parsing.
"""
+import six
+
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
from django.contrib.sessions.middleware import SessionMiddleware
@@ -79,14 +81,14 @@ class TestContentParsing(TestCase):
data = {'qwerty': 'uiop'}
request = Request(factory.post('/', data))
request.parsers = (FormParser(), MultiPartParser())
- self.assertEqual(request.DATA.items(), data.items())
+ self.assertEqual(list(request.DATA.items()), list(data.items()))
def test_request_DATA_with_text_content(self):
"""
Ensure request.DATA returns content for POST request with
non-form content.
"""
- content = 'qwerty'
+ content = six.b('qwerty')
content_type = 'text/plain'
request = Request(factory.post('/', content, content_type=content_type))
request.parsers = (PlainTextParser(),)
@@ -99,7 +101,7 @@ class TestContentParsing(TestCase):
data = {'qwerty': 'uiop'}
request = Request(factory.post('/', data))
request.parsers = (FormParser(), MultiPartParser())
- self.assertEqual(request.POST.items(), data.items())
+ self.assertEqual(list(request.POST.items()), list(data.items()))
def test_standard_behaviour_determines_form_content_PUT(self):
"""
@@ -117,14 +119,14 @@ class TestContentParsing(TestCase):
request = Request(factory.put('/', data))
request.parsers = (FormParser(), MultiPartParser())
- self.assertEqual(request.DATA.items(), data.items())
+ self.assertEqual(list(request.DATA.items()), list(data.items()))
def test_standard_behaviour_determines_non_form_content_PUT(self):
"""
Ensure request.DATA returns content for PUT request with
non-form content.
"""
- content = 'qwerty'
+ content = six.b('qwerty')
content_type = 'text/plain'
request = Request(factory.put('/', content, content_type=content_type))
request.parsers = (PlainTextParser(), )
diff --git a/rest_framework/tests/response.py b/rest_framework/tests/response.py
index 875f4d42..fd153f40 100644
--- a/rest_framework/tests/response.py
+++ b/rest_framework/tests/response.py
@@ -1,3 +1,6 @@
+import unittest
+import six
+
from django.test import TestCase
from rest_framework.compat import patterns, url, include
from rest_framework.response import Response
@@ -22,8 +25,8 @@ class MockJsonRenderer(BaseRenderer):
DUMMYSTATUS = status.HTTP_200_OK
DUMMYCONTENT = 'dummycontent'
-RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
-RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
+RENDERER_A_SERIALIZER = lambda x: ('Renderer A: %s' % x).encode('ascii')
+RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii')
class RendererA(BaseRenderer):
@@ -92,7 +95,7 @@ class RendererIntegrationTests(TestCase):
resp = self.client.head('/')
self.assertEquals(resp.status_code, DUMMYSTATUS)
self.assertEquals(resp['Content-Type'], RendererA.media_type)
- self.assertEquals(resp.content, '')
+ self.assertEquals(resp.content, six.b(''))
def test_default_renderer_serializes_content_on_accept_any(self):
"""If the Accept header is set to */* the default renderer should serialize the response."""
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 701b2f47..4654882e 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import datetime
import pickle
from django.test import TestCase
@@ -188,12 +190,12 @@ class ValidationTests(TestCase):
def test_create(self):
serializer = CommentSerializer(data=self.data)
self.assertEquals(serializer.is_valid(), False)
- self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']})
+ self.assertEquals(serializer.errors, {'content': ['Ensure this value has at most 1000 characters (it has 1001).']})
def test_update(self):
serializer = CommentSerializer(self.comment, data=self.data)
self.assertEquals(serializer.is_valid(), False)
- self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']})
+ self.assertEquals(serializer.errors, {'content': ['Ensure this value has at most 1000 characters (it has 1001).']})
def test_update_missing_field(self):
data = {
@@ -202,7 +204,7 @@ class ValidationTests(TestCase):
}
serializer = CommentSerializer(self.comment, data=data)
self.assertEquals(serializer.is_valid(), False)
- self.assertEquals(serializer.errors, {'email': [u'This field is required.']})
+ self.assertEquals(serializer.errors, {'email': ['This field is required.']})
def test_missing_bool_with_default(self):
"""Make sure that a boolean value with a 'False' value is not
@@ -238,7 +240,7 @@ class ValidationTests(TestCase):
serializer = CommentSerializerWithFieldValidator(data=data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'content': [u'Test not in value']})
+ self.assertEquals(serializer.errors, {'content': ['Test not in value']})
def test_cross_field_validation(self):
@@ -262,7 +264,7 @@ class ValidationTests(TestCase):
serializer = CommentSerializerWithCrossFieldValidator(data=data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'non_field_errors': [u'Email address not in content']})
+ self.assertEquals(serializer.errors, {'non_field_errors': ['Email address not in content']})
def test_null_is_true_fields(self):
"""
@@ -278,7 +280,7 @@ class ValidationTests(TestCase):
}
serializer = ActionItemSerializer(data=data)
self.assertEquals(serializer.is_valid(), False)
- self.assertEquals(serializer.errors, {'title': [u'Ensure this value has at most 200 characters (it has 201).']})
+ self.assertEquals(serializer.errors, {'title': ['Ensure this value has at most 200 characters (it has 201).']})
def test_default_modelfield_max_length_exceeded(self):
data = {
@@ -287,7 +289,7 @@ class ValidationTests(TestCase):
}
serializer = ActionItemSerializer(data=data)
self.assertEquals(serializer.is_valid(), False)
- self.assertEquals(serializer.errors, {'info': [u'Ensure this value has at most 12 characters (it has 13).']})
+ self.assertEquals(serializer.errors, {'info': ['Ensure this value has at most 12 characters (it has 13).']})
class PositiveIntegerAsChoiceTests(TestCase):
@@ -345,15 +347,15 @@ class RegexValidationTest(TestCase):
def test_create_failed(self):
serializer = BookSerializer(data={'isbn': '1234567890'})
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'isbn': [u'isbn has to be exact 13 numbers']})
+ self.assertEquals(serializer.errors, {'isbn': ['isbn has to be exact 13 numbers']})
serializer = BookSerializer(data={'isbn': '12345678901234'})
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'isbn': [u'isbn has to be exact 13 numbers']})
+ self.assertEquals(serializer.errors, {'isbn': ['isbn has to be exact 13 numbers']})
serializer = BookSerializer(data={'isbn': 'abcdefghijklm'})
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'isbn': [u'isbn has to be exact 13 numbers']})
+ self.assertEquals(serializer.errors, {'isbn': ['isbn has to be exact 13 numbers']})
def test_create_success(self):
serializer = BookSerializer(data={'isbn': '1234567890123'})
@@ -691,8 +693,8 @@ class SerializerMethodFieldTests(TestCase):
serializer = self.serializer_class(source_data)
expected = {
- 'beep': u'hello!',
- 'boop': [u'a', u'b', u'c'],
+ 'beep': 'hello!',
+ 'boop': ['a', 'b', 'c'],
'boop_count': 3,
}
diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py
index 43365e07..e51ca9f3 100644
--- a/rest_framework/tests/views.py
+++ b/rest_framework/tests/views.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import copy
from django.test import TestCase
from django.test.client import RequestFactory
@@ -47,7 +49,7 @@ class ClassBasedViewIntegrationTests(TestCase):
request = factory.post('/', 'f00bar', content_type='application/json')
response = self.view(request)
expected = {
- 'detail': u'JSON parse error - No JSON object could be decoded'
+ 'detail': 'JSON parse error - No JSON object could be decoded'
}
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEquals(sanitise_json_error(response.data), expected)
@@ -62,7 +64,7 @@ class ClassBasedViewIntegrationTests(TestCase):
request = factory.post('/', form_data)
response = self.view(request)
expected = {
- 'detail': u'JSON parse error - No JSON object could be decoded'
+ 'detail': 'JSON parse error - No JSON object could be decoded'
}
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEquals(sanitise_json_error(response.data), expected)
@@ -76,7 +78,7 @@ class FunctionBasedViewIntegrationTests(TestCase):
request = factory.post('/', 'f00bar', content_type='application/json')
response = self.view(request)
expected = {
- 'detail': u'JSON parse error - No JSON object could be decoded'
+ 'detail': 'JSON parse error - No JSON object could be decoded'
}
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEquals(sanitise_json_error(response.data), expected)
@@ -91,7 +93,7 @@ class FunctionBasedViewIntegrationTests(TestCase):
request = factory.post('/', form_data)
response = self.view(request)
expected = {
- 'detail': u'JSON parse error - No JSON object could be decoded'
+ 'detail': 'JSON parse error - No JSON object could be decoded'
}
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEquals(sanitise_json_error(response.data), expected)
diff --git a/rest_framework/utils/__init__.py b/rest_framework/utils/__init__.py
index 84fcb5db..45879353 100644
--- a/rest_framework/utils/__init__.py
+++ b/rest_framework/utils/__init__.py
@@ -1,4 +1,11 @@
-from django.utils.encoding import smart_unicode
+
+import six
+
+try:
+ from django.utils.encoding import smart_text
+except ImportError:
+ from django.utils.encoding import smart_unicode as smart_text
+
from django.utils.xmlutils import SimplerXMLGenerator
from rest_framework.compat import StringIO
import re
@@ -70,7 +77,7 @@ class XMLRenderer():
xml.endElement("list-item")
elif isinstance(data, dict):
- for key, value in data.iteritems():
+ for key, value in six.iteritems(data):
xml.startElement(key, {})
self._to_xml(xml, value)
xml.endElement(key)
@@ -80,10 +87,10 @@ class XMLRenderer():
pass
else:
- xml.characters(smart_unicode(data))
+ xml.characters(smart_text(data))
def dict2xml(self, data):
- stream = StringIO.StringIO()
+ stream = StringIO()
xml = SimplerXMLGenerator(stream, "utf-8")
xml.startDocument()
diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py
index ee7f3a54..3fc59edd 100644
--- a/rest_framework/utils/mediatypes.py
+++ b/rest_framework/utils/mediatypes.py
@@ -47,7 +47,7 @@ class _MediaType(object):
if media_type_str is None:
media_type_str = ''
self.orig = media_type_str
- self.full_type, self.params = parse_header(media_type_str)
+ self.full_type, self.params = parse_header(media_type_str.encode('iso-8859-1'))
self.main_type, sep, self.sub_type = self.full_type.partition('/')
def match(self, other):