aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/__init__.py3
-rw-r--r--rest_framework/authentication.py21
-rw-r--r--rest_framework/authtoken/models.py4
-rw-r--r--rest_framework/compat.py46
-rw-r--r--rest_framework/decorators.py4
-rw-r--r--rest_framework/exceptions.py1
-rw-r--r--rest_framework/fields.py89
-rw-r--r--rest_framework/filters.py3
-rw-r--r--rest_framework/generics.py2
-rw-r--r--rest_framework/mixins.py4
-rw-r--r--rest_framework/negotiation.py5
-rw-r--r--rest_framework/pagination.py1
-rw-r--r--rest_framework/parsers.py46
-rw-r--r--rest_framework/permissions.py2
-rw-r--r--rest_framework/relations.py253
-rw-r--r--rest_framework/renderers.py11
-rw-r--r--rest_framework/request.py13
-rw-r--r--rest_framework/response.py6
-rw-r--r--rest_framework/reverse.py1
-rwxr-xr-xrest_framework/runtests/runtests.py2
-rw-r--r--rest_framework/serializers.py110
-rw-r--r--rest_framework/settings.py4
-rw-r--r--rest_framework/six.py389
-rw-r--r--rest_framework/status.py1
-rw-r--r--rest_framework/templatetags/rest_framework.py16
-rw-r--r--rest_framework/tests/authentication.py16
-rw-r--r--rest_framework/tests/breadcrumbs.py1
-rw-r--r--rest_framework/tests/decorators.py1
-rw-r--r--rest_framework/tests/description.py1
-rw-r--r--rest_framework/tests/fields.py2
-rw-r--r--rest_framework/tests/files.py12
-rw-r--r--rest_framework/tests/filterset.py1
-rw-r--r--rest_framework/tests/genericrelations.py17
-rw-r--r--rest_framework/tests/generics.py11
-rw-r--r--rest_framework/tests/htmlrenderer.py10
-rw-r--r--rest_framework/tests/hyperlinkedserializers.py1
-rw-r--r--rest_framework/tests/models.py31
-rw-r--r--rest_framework/tests/modelviews.py90
-rw-r--r--rest_framework/tests/negotiation.py9
-rw-r--r--rest_framework/tests/pagination.py1
-rw-r--r--rest_framework/tests/parsers.py136
-rw-r--r--rest_framework/tests/relations.py2
-rw-r--r--rest_framework/tests/relations_hyperlink.py171
-rw-r--r--rest_framework/tests/relations_nested.py27
-rw-r--r--rest_framework/tests/relations_pk.py171
-rw-r--r--rest_framework/tests/relations_slug.py108
-rw-r--r--rest_framework/tests/renderers.py18
-rw-r--r--rest_framework/tests/request.py14
-rw-r--r--rest_framework/tests/response.py8
-rw-r--r--rest_framework/tests/reverse.py1
-rw-r--r--rest_framework/tests/serializer.py61
-rw-r--r--rest_framework/tests/settings.py1
-rw-r--r--rest_framework/tests/status.py1
-rw-r--r--rest_framework/tests/testcases.py1
-rw-r--r--rest_framework/tests/tests.py1
-rw-r--r--rest_framework/tests/throttling.py3
-rw-r--r--rest_framework/tests/urlpatterns.py4
-rw-r--r--rest_framework/tests/utils.py5
-rw-r--r--rest_framework/tests/validators.py2
-rw-r--r--rest_framework/tests/views.py11
-rw-r--r--rest_framework/throttling.py3
-rw-r--r--rest_framework/urlpatterns.py3
-rw-r--r--rest_framework/urls.py1
-rw-r--r--rest_framework/utils/__init__.py10
-rw-r--r--rest_framework/utils/breadcrumbs.py1
-rw-r--r--rest_framework/utils/encoders.py7
-rw-r--r--rest_framework/utils/mediatypes.py5
-rw-r--r--rest_framework/views.py4
68 files changed, 1147 insertions, 874 deletions
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index f9882c57..80e2c410 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,3 +1,6 @@
__version__ = '2.1.17'
VERSION = __version__ # synonym
+
+# Header encoding (see RFC5987)
+HTTP_HEADER_ENCODING = 'iso-8859-1'
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index fc169189..14b2136b 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -1,10 +1,10 @@
"""
Provides a set of pluggable authentication policies.
"""
-
+from __future__ import unicode_literals
from django.contrib.auth import authenticate
-from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError
-from rest_framework import exceptions
+from django.utils.encoding import DjangoUnicodeDecodeError
+from rest_framework import exceptions, HTTP_HEADER_ENCODING
from rest_framework.compat import CsrfViewMiddleware
from rest_framework.authtoken.models import Token
import base64
@@ -41,22 +41,25 @@ class BasicAuthentication(BaseAuthentication):
Returns a `User` if a correct username and password have been supplied
using HTTP Basic authentication. Otherwise returns `None`.
"""
- auth = request.META.get('HTTP_AUTHORIZATION', '').split()
+ auth = request.META.get('HTTP_AUTHORIZATION', b'')
+ if type(auth) == type(''):
+ # Work around django test client oddness
+ auth = auth.encode(HTTP_HEADER_ENCODING)
+ auth = auth.split()
- if not auth or auth[0].lower() != "basic":
+ if not auth or auth[0].lower() != b'basic':
return None
if len(auth) != 2:
raise exceptions.AuthenticationFailed('Invalid basic header')
try:
- auth_parts = base64.b64decode(auth[1]).partition(':')
- except TypeError:
+ auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
+ except (TypeError, UnicodeDecodeError):
raise exceptions.AuthenticationFailed('Invalid basic header')
try:
- userid = smart_unicode(auth_parts[0])
- password = smart_unicode(auth_parts[2])
+ userid, password = auth_parts[0], auth_parts[2]
except DjangoUnicodeDecodeError:
raise exceptions.AuthenticationFailed('Invalid basic header')
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 5508f6c0..8c64d951 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -3,14 +3,35 @@ 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 django
+# Try to import six from Django, fallback to included `six`.
+try:
+ from django.utils import six
+except:
+ from rest_framework import six
+
# location of patterns, url, include changes in 1.4 onwards
try:
from django.conf.urls import patterns, url, include
except:
from django.conf.urls.defaults import patterns, url, include
+# Handle django.utils.encoding rename:
+# smart_unicode -> smart_text
+# force_unicode -> force_text
+try:
+ from django.utils.encoding import smart_text
+except ImportError:
+ from django.utils.encoding import smart_unicode as smart_text
+try:
+ from django.utils.encoding import force_text
+except ImportError:
+ from django.utils.encoding import force_unicode as force_text
+
+
# django-filter is optional
try:
import django_filters
@@ -20,9 +41,18 @@ except:
# cStringIO only if it's available, otherwise StringIO
try:
- import cStringIO as StringIO
+ import cStringIO.StringIO as StringIO
except ImportError:
- import StringIO
+ StringIO = six.StringIO
+
+BytesIO = six.BytesIO
+
+
+# urlparse compat import (Required because it changed in python 3.x)
+try:
+ from urllib import parse as urlparse
+except ImportError:
+ import urlparse
# Try to import PIL in either of the two ways it can end up installed.
@@ -54,7 +84,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 +105,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):
@@ -110,7 +140,6 @@ else:
import re
import random
import logging
- import urlparse
from django.conf import settings
from django.core.urlresolvers import get_callable
@@ -152,7 +181,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/decorators.py b/rest_framework/decorators.py
index 7a4103e1..8250cd3b 100644
--- a/rest_framework/decorators.py
+++ b/rest_framework/decorators.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+from rest_framework.compat import six
from rest_framework.views import APIView
import types
@@ -12,7 +14,7 @@ def api_view(http_method_names):
def decorator(func):
WrappedAPIView = type(
- 'WrappedAPIView',
+ six.PY3 and 'WrappedAPIView' or b'WrappedAPIView',
(APIView,),
{'__doc__': func.__doc__}
)
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index d635351c..0c96ecdd 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -4,6 +4,7 @@ Handled exceptions raised by REST framework.
In addition Django's built in 403 and 404 exceptions are handled.
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
"""
+from __future__ import unicode_literals
from rest_framework import status
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 998911e1..d6e8539d 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1,20 +1,23 @@
+from __future__ import unicode_literals
+
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
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
+from rest_framework.compat import six
+from rest_framework.compat import smart_text
def is_simple_callable(obj):
@@ -32,7 +35,8 @@ class Field(object):
creation_counter = 0
empty = ''
type_name = None
- _use_files = None
+ partial = False
+ use_files = False
form_field_class = forms.CharField
def __init__(self, source=None):
@@ -53,7 +57,8 @@ class Field(object):
self.parent = parent
self.root = parent.root or parent
self.context = self.root.context
- if self.root.partial:
+ self.partial = self.root.partial
+ if self.partial:
self.required = False
def field_from_native(self, data, files, field_name, into):
@@ -93,11 +98,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):
"""
@@ -124,6 +129,13 @@ class WritableField(Field):
validators=[], error_messages=None, widget=None,
default=None, blank=None):
+ # 'blank' is to be deprecated in favor of 'required'
+ if blank is not None:
+ warnings.warn('The `blank` keyword argument is due to deprecated. '
+ 'Use the `required` keyword argument instead.',
+ PendingDeprecationWarning, stacklevel=2)
+ required = not(blank)
+
super(WritableField, self).__init__(source=source)
self.read_only = read_only
@@ -141,7 +153,6 @@ class WritableField(Field):
self.validators = self.default_validators + validators
self.default = default if default is not None else self.default
- self.blank = blank
# Widgets are ony used for HTML forms.
widget = widget or self.widget
@@ -180,13 +191,13 @@ class WritableField(Field):
return
try:
- if self._use_files:
+ if self.use_files:
files = files or {}
native = files[field_name]
else:
native = data[field_name]
except KeyError:
- if self.default is not None and not self.root.partial:
+ if self.default is not None and not self.partial:
# Note: partial updates shouldn't set defaults
native = self.default
else:
@@ -258,7 +269,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
@@ -287,20 +298,10 @@ class CharField(WritableField):
if max_length is not None:
self.validators.append(validators.MaxLengthValidator(max_length))
- def validate(self, value):
- """
- Validates that the value is supplied (if required).
- """
- # if empty string and allow blank
- if self.blank and not value:
- return
- else:
- 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):
@@ -325,7 +326,8 @@ class ChoiceField(WritableField):
form_field_class = forms.ChoiceField
widget = widgets.Select
default_error_messages = {
- 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'),
+ 'invalid_choice': _('Select a valid choice. %(value)s is not one of '
+ 'the available choices.'),
}
def __init__(self, choices=(), *args, **kwargs):
@@ -359,10 +361,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
@@ -402,7 +404,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:
@@ -425,10 +427,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
@@ -464,13 +466,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
@@ -487,8 +489,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)
@@ -564,7 +566,7 @@ class FloatField(WritableField):
class FileField(WritableField):
- _use_files = True
+ use_files = True
type_name = 'FileField'
form_field_class = forms.FileField
widget = widgets.FileInput
@@ -608,11 +610,12 @@ class FileField(WritableField):
class ImageField(FileField):
- _use_files = True
+ use_files = True
form_field_class = forms.ImageField
default_error_messages = {
- 'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
+ 'invalid_image': _("Upload a valid image. The file you uploaded was "
+ "either not an image or a corrupted image."),
}
def from_native(self, data):
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index bcc87660..6fea46fa 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from rest_framework.compat import django_filters
FilterSet = django_filters and django_filters.FilterSet or None
@@ -54,6 +55,6 @@ class DjangoFilterBackend(BaseFilterBackend):
filter_class = self.get_filter_class(view)
if filter_class:
- return filter_class(request.GET, queryset=queryset)
+ return filter_class(request.QUERY_PARAMS, queryset=queryset)
return queryset
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 19f2b704..9e931917 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -1,7 +1,7 @@
"""
Generic views that provide commonly needed behaviour.
"""
-
+from __future__ import unicode_literals
from rest_framework import views, mixins
from rest_framework.settings import api_settings
from django.views.generic.detail import SingleObjectMixin
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 43581ae9..acaf8a71 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
@@ -41,7 +43,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/negotiation.py b/rest_framework/negotiation.py
index ee2800a6..0694d35f 100644
--- a/rest_framework/negotiation.py
+++ b/rest_framework/negotiation.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.http import Http404
from rest_framework import exceptions
from rest_framework.settings import api_settings
@@ -33,7 +34,7 @@ class DefaultContentNegotiation(BaseContentNegotiation):
"""
# Allow URL style format override. eg. "?format=json
format_query_param = self.settings.URL_FORMAT_OVERRIDE
- format = format_suffix or request.GET.get(format_query_param)
+ format = format_suffix or request.QUERY_PARAMS.get(format_query_param)
if format:
renderers = self.filter_renderers(renderers, format)
@@ -80,5 +81,5 @@ class DefaultContentNegotiation(BaseContentNegotiation):
Allows URL style accept override. eg. "?accept=application/json"
"""
header = request.META.get('HTTP_ACCEPT', '*/*')
- header = request.GET.get(self.settings.URL_ACCEPT_OVERRIDE, header)
+ header = request.QUERY_PARAMS.get(self.settings.URL_ACCEPT_OVERRIDE, header)
return [token.strip() for token in header.split(',')]
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index 92d41e0e..03a7a30f 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from rest_framework import serializers
from rest_framework.templatetags.rest_framework import replace_query_param
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index 149d6431..98d63fec 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -4,12 +4,14 @@ Parsers are used to parse the content of incoming HTTP requests.
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.
"""
-
+from __future__ import unicode_literals
+from django.conf import settings
from django.http import QueryDict
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError
from rest_framework.compat import yaml, ETParseError
from rest_framework.exceptions import ParseError
+from rest_framework.compat import six
from xml.etree import ElementTree as ET
from xml.parsers.expat import ExpatError
import json
@@ -54,10 +56,14 @@ class JSONParser(BaseParser):
`data` will be an object which is the parsed content of the response.
`files` will always be `None`.
"""
+ parser_context = parser_context or {}
+ encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
+
try:
- return json.load(stream)
- except ValueError, exc:
- raise ParseError('JSON parse error - %s' % unicode(exc))
+ data = stream.read().decode(encoding)
+ return json.loads(data)
+ except ValueError as exc:
+ raise ParseError('JSON parse error - %s' % six.text_type(exc))
class YAMLParser(BaseParser):
@@ -74,10 +80,14 @@ class YAMLParser(BaseParser):
`data` will be an object which is the parsed content of the response.
`files` will always be `None`.
"""
+ parser_context = parser_context or {}
+ encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
+
try:
- return yaml.safe_load(stream)
- except (ValueError, yaml.parser.ParserError), exc:
- raise ParseError('YAML parse error - %s' % unicode(exc))
+ data = stream.read().decode(encoding)
+ return yaml.safe_load(data)
+ except (ValueError, yaml.parser.ParserError) as exc:
+ raise ParseError('YAML parse error - %s' % six.u(exc))
class FormParser(BaseParser):
@@ -94,7 +104,9 @@ class FormParser(BaseParser):
`data` will be a :class:`QueryDict` containing all the form parameters.
`files` will always be :const:`None`.
"""
- data = QueryDict(stream.read())
+ parser_context = parser_context or {}
+ encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
+ data = QueryDict(stream.read(), encoding=encoding)
return data
@@ -114,15 +126,16 @@ class MultiPartParser(BaseParser):
"""
parser_context = parser_context or {}
request = parser_context['request']
+ encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
meta = request.META
upload_handlers = request.upload_handlers
try:
- parser = DjangoMultiPartParser(meta, stream, upload_handlers)
+ parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding)
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):
@@ -133,10 +146,13 @@ class XMLParser(BaseParser):
media_type = 'application/xml'
def parse(self, stream, media_type=None, parser_context=None):
+ parser_context = parser_context or {}
+ encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
+ parser = ET.XMLParser(encoding=encoding)
try:
- tree = ET.parse(stream)
- except (ExpatError, ETParseError, ValueError), exc:
- raise ParseError('XML parse error - %s' % unicode(exc))
+ tree = ET.parse(stream, parser=parser)
+ except (ExpatError, ETParseError, ValueError) as exc:
+ raise ParseError('XML parse error - %s' % six.u(exc))
data = self._xml_convert(tree.getroot())
return data
@@ -146,7 +162,7 @@ class XMLParser(BaseParser):
convert the xml `element` into the corresponding python object
"""
- children = element.getchildren()
+ children = list(element)
if len(children) == 0:
return self._type_convert(element.text)
diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py
index 655b78a3..3222dbf2 100644
--- a/rest_framework/permissions.py
+++ b/rest_framework/permissions.py
@@ -1,7 +1,7 @@
"""
Provides a set of pluggable permission policies.
"""
-
+from __future__ import unicode_literals
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index dc0a73e6..ae4ef6b3 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,13 +1,16 @@
+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
from django.utils.translation import ugettext_lazy as _
from rest_framework.fields import Field, WritableField
from rest_framework.reverse import reverse
-from urlparse import urlparse
+from rest_framework.compat import urlparse
+from rest_framework.compat import smart_text
+import warnings
+
##### Relational fields #####
@@ -17,19 +20,35 @@ class RelatedField(WritableField):
"""
Base class for related model fields.
- If not overridden, this represents a to-one relationship, using the unicode
- representation of the target.
+ This represents a relationship using the unicode representation of the target.
"""
widget = widgets.Select
+ many_widget = widgets.SelectMultiple
+ form_field_class = forms.ChoiceField
+ many_form_field_class = forms.MultipleChoiceField
+
cache_choices = False
empty_label = None
- default_read_only = True # TODO: Remove this
+ read_only = True
+ many = False
def __init__(self, *args, **kwargs):
+
+ # 'null' is to be deprecated in favor of 'required'
+ if 'null' in kwargs:
+ warnings.warn('The `null` keyword argument is due to be deprecated. '
+ 'Use the `required` keyword argument instead.',
+ PendingDeprecationWarning, stacklevel=2)
+ kwargs['required'] = not kwargs.pop('null')
+
self.queryset = kwargs.pop('queryset', None)
- self.null = kwargs.pop('null', False)
+ self.many = kwargs.pop('many', self.many)
+ if self.many:
+ self.widget = self.many_widget
+ self.form_field_class = self.many_form_field_class
+
+ kwargs['read_only'] = kwargs.pop('read_only', self.read_only)
super(RelatedField, self).__init__(*args, **kwargs)
- self.read_only = kwargs.pop('read_only', self.default_read_only)
def initialize(self, parent, field_name):
super(RelatedField, self).initialize(parent, field_name)
@@ -48,11 +67,6 @@ class RelatedField(WritableField):
### We need this stuff to make form choices work...
- # def __deepcopy__(self, memo):
- # result = super(RelatedField, self).__deepcopy__(memo)
- # result.queryset = result.queryset
- # return result
-
def prepare_value(self, obj):
return self.to_native(obj)
@@ -60,8 +74,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)
@@ -108,6 +122,9 @@ class RelatedField(WritableField):
if value is None:
return None
+
+ if self.many:
+ return [self.to_native(item) for item in value.all()]
return self.to_native(value)
def field_from_native(self, data, files, field_name, into):
@@ -115,65 +132,39 @@ class RelatedField(WritableField):
return
try:
- value = data[field_name]
+ if self.many:
+ try:
+ # Form data
+ value = data.getlist(field_name)
+ if value == [''] or value == []:
+ raise KeyError
+ except AttributeError:
+ # Non-form data
+ value = data[field_name]
+ else:
+ value = data[field_name]
except KeyError:
- if self.required:
- raise ValidationError(self.error_messages['required'])
- return
+ if self.partial:
+ return
+ value = [] if self.many else None
- if value in (None, '') and not self.null:
- raise ValidationError('Value may not be null')
- elif value in (None, '') and self.null:
+ if value in (None, '') and self.required:
+ raise ValidationError(self.error_messages['required'])
+ elif value in (None, ''):
into[(self.source or field_name)] = None
+ elif self.many:
+ into[(self.source or field_name)] = [self.from_native(item) for item in value]
else:
into[(self.source or field_name)] = self.from_native(value)
-class ManyRelatedMixin(object):
- """
- Mixin to convert a related field to a many related field.
- """
- widget = widgets.SelectMultiple
-
- def field_to_native(self, obj, field_name):
- value = getattr(obj, self.source or field_name)
- return [self.to_native(item) for item in value.all()]
-
- def field_from_native(self, data, files, field_name, into):
- if self.read_only:
- return
-
- try:
- # Form data
- value = data.getlist(self.source or field_name)
- except:
- # Non-form data
- value = data.get(self.source or field_name, [])
- else:
- if value == ['']:
- value = []
-
- into[field_name] = [self.from_native(item) for item in value]
-
-
-class ManyRelatedField(ManyRelatedMixin, RelatedField):
- """
- Base class for related model managers.
-
- If not overridden, this represents a to-many relationship, using the unicode
- representations of the target, and is read-only.
- """
- pass
-
-
### PrimaryKey relationships
class PrimaryKeyRelatedField(RelatedField):
"""
- Represents a to-one relationship as a pk value.
+ Represents a relationship as a pk value.
"""
- default_read_only = False
- form_field_class = forms.ChoiceField
+ read_only = False
default_error_messages = {
'does_not_exist': _("Invalid pk '%s' - object does not exist."),
@@ -188,8 +179,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)
@@ -205,7 +196,7 @@ class PrimaryKeyRelatedField(RelatedField):
try:
return self.queryset.get(pk=data)
except ObjectDoesNotExist:
- msg = self.error_messages['does_not_exist'] % smart_unicode(data)
+ msg = self.error_messages['does_not_exist'] % smart_text(data)
raise ValidationError(msg)
except (TypeError, ValueError):
received = type(data).__name__
@@ -213,79 +204,42 @@ class PrimaryKeyRelatedField(RelatedField):
raise ValidationError(msg)
def field_to_native(self, obj, field_name):
+ if self.many:
+ # To-many relationship
+ try:
+ # Prefer obj.serializable_value for performance reasons
+ queryset = obj.serializable_value(self.source or field_name)
+ except AttributeError:
+ # RelatedManager (reverse relationship)
+ queryset = getattr(obj, self.source or field_name)
+
+ # Forward relationship
+ return [self.to_native(item.pk) for item in queryset.all()]
+
+ # To-one relationship
try:
# Prefer obj.serializable_value for performance reasons
pk = obj.serializable_value(self.source or field_name)
except AttributeError:
# RelatedObject (reverse relationship)
try:
- obj = getattr(obj, self.source or field_name)
+ pk = getattr(obj, self.source or field_name).pk
except ObjectDoesNotExist:
return None
return self.to_native(obj.pk)
- # Forward relationship
- return self.to_native(pk)
-
-
-class ManyPrimaryKeyRelatedField(ManyRelatedField):
- """
- Represents a to-many relationship as a pk value.
- """
- default_read_only = False
- form_field_class = forms.MultipleChoiceField
-
- default_error_messages = {
- 'does_not_exist': _("Invalid pk '%s' - object does not exist."),
- 'incorrect_type': _('Incorrect type. Expected pk value, received %s.'),
- }
-
- def prepare_value(self, obj):
- return self.to_native(obj.pk)
-
- def label_from_instance(self, obj):
- """
- Return a readable representation for use with eg. select widgets.
- """
- desc = smart_unicode(obj)
- ident = smart_unicode(self.to_native(obj.pk))
- if desc == ident:
- return desc
- return "%s - %s" % (desc, ident)
- def to_native(self, pk):
- return pk
-
- def field_to_native(self, obj, field_name):
- try:
- # Prefer obj.serializable_value for performance reasons
- queryset = obj.serializable_value(self.source or field_name)
- except AttributeError:
- # RelatedManager (reverse relationship)
- queryset = getattr(obj, self.source or field_name)
- return [self.to_native(item.pk) for item in queryset.all()]
# Forward relationship
- return [self.to_native(item.pk) for item in queryset.all()]
-
- def from_native(self, data):
- if self.queryset is None:
- raise Exception('Writable related fields must include a `queryset` argument')
+ return self.to_native(pk)
- try:
- return self.queryset.get(pk=data)
- except ObjectDoesNotExist:
- msg = self.error_messages['does_not_exist'] % smart_unicode(data)
- raise ValidationError(msg)
- except (TypeError, ValueError):
- received = type(data).__name__
- msg = self.error_messages['incorrect_type'] % received
- raise ValidationError(msg)
### Slug relationships
class SlugRelatedField(RelatedField):
- default_read_only = False
- form_field_class = forms.ChoiceField
+ """
+ Represents a relationship using a unique field on the target.
+ """
+ read_only = False
default_error_messages = {
'does_not_exist': _("Object with %s=%s does not exist."),
@@ -308,27 +262,22 @@ class SlugRelatedField(RelatedField):
return self.queryset.get(**{self.slug_field: data})
except ObjectDoesNotExist:
raise ValidationError(self.error_messages['does_not_exist'] %
- (self.slug_field, unicode(data)))
+ (self.slug_field, smart_text(data)))
except (TypeError, ValueError):
msg = self.error_messages['invalid']
raise ValidationError(msg)
-class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField):
- form_field_class = forms.MultipleChoiceField
-
-
### Hyperlinked relationships
class HyperlinkedRelatedField(RelatedField):
"""
- Represents a to-one relationship, using hyperlinking.
+ Represents a relationship using hyperlinking.
"""
pk_url_kwarg = 'pk'
slug_field = 'slug'
slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
- default_read_only = False
- form_field_class = forms.ChoiceField
+ read_only = False
default_error_messages = {
'no_match': _('Invalid hyperlink - No URL match'),
@@ -404,7 +353,7 @@ class HyperlinkedRelatedField(RelatedField):
if http_prefix:
# If needed convert absolute URLs to relative path
- value = urlparse(value).path
+ value = urlparse.urlparse(value).path
prefix = get_script_prefix()
if value.startswith(prefix):
value = '/' + value[len(prefix):]
@@ -442,13 +391,6 @@ class HyperlinkedRelatedField(RelatedField):
return obj
-class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField):
- """
- Represents a to-many relationship, using hyperlinking.
- """
- form_field_class = forms.MultipleChoiceField
-
-
class HyperlinkedIdentityField(Field):
"""
Represents the instance, or a property on the instance, using hyperlinking.
@@ -456,6 +398,7 @@ class HyperlinkedIdentityField(Field):
pk_url_kwarg = 'pk'
slug_field = 'slug'
slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
+ read_only = True
def __init__(self, *args, **kwargs):
# TODO: Make view_name mandatory, and have the
@@ -512,3 +455,41 @@ class HyperlinkedIdentityField(Field):
pass
raise Exception('Could not resolve URL for field using view name "%s"' % view_name)
+
+
+### Old-style many classes for backwards compat
+
+class ManyRelatedField(RelatedField):
+ def __init__(self, *args, **kwargs):
+ warnings.warn('`ManyRelatedField()` is due to be deprecated. '
+ 'Use `RelatedField(many=True)` instead.',
+ PendingDeprecationWarning, stacklevel=2)
+ kwargs['many'] = True
+ super(ManyRelatedField, self).__init__(*args, **kwargs)
+
+
+class ManyPrimaryKeyRelatedField(PrimaryKeyRelatedField):
+ def __init__(self, *args, **kwargs):
+ warnings.warn('`ManyPrimaryKeyRelatedField()` is due to be deprecated. '
+ 'Use `PrimaryKeyRelatedField(many=True)` instead.',
+ PendingDeprecationWarning, stacklevel=2)
+ kwargs['many'] = True
+ super(ManyPrimaryKeyRelatedField, self).__init__(*args, **kwargs)
+
+
+class ManySlugRelatedField(SlugRelatedField):
+ def __init__(self, *args, **kwargs):
+ warnings.warn('`ManySlugRelatedField()` is due to be deprecated. '
+ 'Use `SlugRelatedField(many=True)` instead.',
+ PendingDeprecationWarning, stacklevel=2)
+ kwargs['many'] = True
+ super(ManySlugRelatedField, self).__init__(*args, **kwargs)
+
+
+class ManyHyperlinkedRelatedField(HyperlinkedRelatedField):
+ def __init__(self, *args, **kwargs):
+ warnings.warn('`ManyHyperlinkedRelatedField()` is due to be deprecated. '
+ 'Use `HyperlinkedRelatedField(many=True)` instead.',
+ PendingDeprecationWarning, stacklevel=2)
+ kwargs['many'] = True
+ super(ManyHyperlinkedRelatedField, self).__init__(*args, **kwargs)
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 0a34abaa..74c7e2c9 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
import json
@@ -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)
@@ -86,7 +88,7 @@ class JSONPRenderer(JSONRenderer):
Determine the name of the callback to wrap around the json output.
"""
request = renderer_context.get('request', None)
- params = request and request.GET or {}
+ params = request and request.QUERY_PARAMS or {}
return params.get(self.callback_parameter, self.default_callback)
def render(self, data, accepted_media_type=None, renderer_context=None):
@@ -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):
@@ -333,6 +335,7 @@ class BrowsableAPIRenderer(BaseRenderer):
kwargs['label'] = k
fields[k] = v.form_field_class(**kwargs)
+
return fields
def get_form(self, view, method, request):
@@ -357,7 +360,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 1c28cd17..482c8688 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -9,10 +9,12 @@ 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
-
+from __future__ import unicode_literals
+from django.conf import settings
from django.http.multipartparser import parse_header
+from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import exceptions
+from rest_framework.compat import BytesIO
from rest_framework.settings import api_settings
@@ -20,7 +22,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(HTTP_HEADER_ENCODING))
return (base_media_type == 'application/x-www-form-urlencoded' or
base_media_type == 'multipart/form-data')
@@ -91,6 +93,7 @@ class Request(object):
if self.parser_context is None:
self.parser_context = {}
self.parser_context['request'] = self
+ self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
def _default_negotiator(self):
return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
@@ -242,7 +245,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):
"""
@@ -277,7 +280,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(HTTP_HEADER_ENCODING))
self._data, self._files = (Empty, Empty)
def _parse(self):
diff --git a/rest_framework/response.py b/rest_framework/response.py
index be78c43a..5e1bf46e 100644
--- a/rest_framework/response.py
+++ b/rest_framework/response.py
@@ -1,5 +1,7 @@
+from __future__ import unicode_literals
from django.core.handlers.wsgi import STATUS_CODE_TEXT
from django.template.response import SimpleTemplateResponse
+from rest_framework.compat import six
class Response(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/reverse.py b/rest_framework/reverse.py
index c9db02f0..a51b07f5 100644
--- a/rest_framework/reverse.py
+++ b/rest_framework/reverse.py
@@ -1,6 +1,7 @@
"""
Provide reverse functions that return fully qualified URLs
"""
+from __future__ import unicode_literals
from django.core.urlresolvers import reverse as django_reverse
from django.utils.functional import lazy
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 4fb802a7..b635d20d 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
import copy
import datetime
import types
@@ -7,6 +8,7 @@ from django.db import models
from django.forms import widgets
from django.utils.datastructures import SortedDict
from rest_framework.compat import get_concrete_model
+from rest_framework.compat import six
# Note: We do the following so that users of the framework can use this style:
#
@@ -64,7 +66,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)
@@ -73,7 +75,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)
@@ -95,19 +97,24 @@ class SerializerOptions(object):
class BaseSerializer(Field):
+ """
+ This is the Serializer implementation.
+ We need to implement it as `BaseSerializer` due to metaclass magicks.
+ """
class Meta(object):
pass
_options_class = SerializerOptions
- _dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatibility with unsorted implementations.
+ _dict_class = SortedDictWithMetadata
def __init__(self, instance=None, data=None, files=None,
- context=None, partial=False, **kwargs):
- super(BaseSerializer, self).__init__(**kwargs)
+ context=None, partial=False, many=None, source=None):
+ super(BaseSerializer, self).__init__(source=source)
self.opts = self._options_class(self.Meta)
self.parent = None
self.root = None
self.partial = partial
+ self.many = many
self.context = context or {}
@@ -187,22 +194,6 @@ class BaseSerializer(Field):
"""
return field_name
- def convert_object(self, obj):
- """
- Core of serialization.
- Convert an object into a dictionary of serialized field values.
- """
- ret = self._dict_class()
- ret.fields = {}
-
- for field_name, field in self.fields.items():
- field.initialize(parent=self, field_name=field_name)
- key = self.get_field_key(field_name)
- value = field.field_to_native(obj, field_name)
- ret[key] = value
- ret.fields[key] = field
- return ret
-
def restore_fields(self, data, files):
"""
Core of deserialization, together with `restore_object`.
@@ -211,7 +202,7 @@ class BaseSerializer(Field):
reverted_data = {}
if data is not None and not isinstance(data, dict):
- self._errors['non_field_errors'] = [u'Invalid data']
+ self._errors['non_field_errors'] = ['Invalid data']
return None
for field_name, field in self.fields.items():
@@ -274,19 +265,22 @@ class BaseSerializer(Field):
"""
Serialize objects -> primitives.
"""
- # Note: At the moment we have an ugly hack to determine if we should
- # walk over iterables. At some point, serializers will require an
- # explicit `many=True` in order to iterate over a set, and this hack
- # will disappear.
- if hasattr(obj, '__iter__') and not isinstance(obj, Page):
- return [self.convert_object(item) for item in obj]
- return self.convert_object(obj)
+ ret = self._dict_class()
+ ret.fields = {}
+
+ for field_name, field in self.fields.items():
+ field.initialize(parent=self, field_name=field_name)
+ key = self.get_field_key(field_name)
+ value = field.field_to_native(obj, field_name)
+ ret[key] = value
+ ret.fields[key] = field
+ return ret
def from_native(self, data, files):
"""
Deserialize primitives -> objects.
"""
- if hasattr(data, '__iter__') and not isinstance(data, dict):
+ if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)):
# TODO: error data when deserializing lists
return [self.from_native(item, None) for item in data]
@@ -328,6 +322,13 @@ class BaseSerializer(Field):
if obj is None:
return None
+ if self.many is not None:
+ many = self.many
+ else:
+ many = hasattr(obj, '__iter__') and not isinstance(obj, Page)
+
+ if many:
+ return [self.to_native(item) for item in obj]
return self.to_native(obj)
@property
@@ -337,9 +338,20 @@ class BaseSerializer(Field):
setting self.object if no errors occurred.
"""
if self._errors is None:
- obj = self.from_native(self.init_data, self.init_files)
+ data, files = self.init_data, self.init_files
+
+ if self.many is not None:
+ many = self.many
+ else:
+ many = hasattr(data, '__iter__') and not isinstance(data, dict)
+
+ # TODO: error data when deserializing lists
+ if many:
+ ret = [self.from_native(item, None) for item in data]
+ ret = self.from_native(data, files)
+
if not self._errors:
- self.object = obj
+ self.object = ret
return self._errors
def is_valid(self):
@@ -347,8 +359,22 @@ class BaseSerializer(Field):
@property
def data(self):
+ """
+ Returns the serialized data on the serializer.
+ """
if self._data is None:
- self._data = self.to_native(self.object)
+ obj = self.object
+
+ if self.many is not None:
+ many = self.many
+ else:
+ many = hasattr(obj, '__iter__') and not isinstance(obj, Page)
+
+ if many:
+ self._data = [self.to_native(item) for item in obj]
+ else:
+ self._data = self.to_native(obj)
+
return self._data
def save(self):
@@ -359,8 +385,8 @@ class BaseSerializer(Field):
return self.object
-class Serializer(BaseSerializer):
- __metaclass__ = SerializerMetaclass
+class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)):
+ pass
class ModelSerializerOptions(SerializerOptions):
@@ -443,7 +469,7 @@ class ModelSerializer(Serializer):
# TODO: filter queryset using:
# .using(db).complex_filter(self.rel.limit_choices_to)
kwargs = {
- 'null': model_field.null or model_field.blank,
+ 'required': not(model_field.null or model_field.blank),
'queryset': model_field.rel.to._default_manager
}
@@ -524,7 +550,7 @@ class ModelSerializer(Serializer):
"""
try:
instance.full_clean(exclude=self.get_validation_exclusions())
- except ValidationError, err:
+ except ValidationError as err:
self._errors = err.message_dict
return None
return instance
@@ -560,6 +586,12 @@ class ModelSerializer(Serializer):
else:
instance = self.opts.model(**attrs)
+ try:
+ instance.full_clean(exclude=self.get_validation_exclusions())
+ except ValidationError as err:
+ self._errors = err.message_dict
+ return None
+
return instance
def from_native(self, data, files):
@@ -600,6 +632,8 @@ class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
class HyperlinkedModelSerializer(ModelSerializer):
"""
+ A subclass of ModelSerializer that uses hyperlinked relationships,
+ instead of primary key relationships.
"""
_options_class = HyperlinkedModelSerializerOptions
_default_view_name = '%(model_name)s-detail'
@@ -633,7 +667,7 @@ class HyperlinkedModelSerializer(ModelSerializer):
# .using(db).complex_filter(self.rel.limit_choices_to)
rel = model_field.rel.to
kwargs = {
- 'null': model_field.null,
+ 'required': not(model_field.null or model_field.blank),
'queryset': rel._default_manager,
'view_name': self._get_default_view_name(rel)
}
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index 5c77c55c..b7aa0bbe 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -17,8 +17,10 @@ This module provides the `api_setting` object, that is used to access
REST framework settings, checking for user settings first, then falling
back to the defaults.
"""
+from __future__ import unicode_literals
from django.conf import settings
from django.utils import importlib
+from rest_framework.compat import six
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, six.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/six.py b/rest_framework/six.py
new file mode 100644
index 00000000..9e382312
--- /dev/null
+++ b/rest_framework/six.py
@@ -0,0 +1,389 @@
+"""Utilities for writing code that runs on Python 2 and 3"""
+
+import operator
+import sys
+import types
+
+__author__ = "Benjamin Peterson <benjamin@python.org>"
+__version__ = "1.2.0"
+
+
+# True if we are running on Python 3.
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+ string_types = str,
+ integer_types = int,
+ class_types = type,
+ text_type = str
+ binary_type = bytes
+
+ MAXSIZE = sys.maxsize
+else:
+ string_types = basestring,
+ integer_types = (int, long)
+ class_types = (type, types.ClassType)
+ text_type = unicode
+ binary_type = str
+
+ if sys.platform == "java":
+ # Jython always uses 32 bits.
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+ class X(object):
+ def __len__(self):
+ return 1 << 31
+ try:
+ len(X())
+ except OverflowError:
+ # 32-bit
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # 64-bit
+ MAXSIZE = int((1 << 63) - 1)
+ del X
+
+
+def _add_doc(func, doc):
+ """Add documentation to a function."""
+ func.__doc__ = doc
+
+
+def _import_module(name):
+ """Import module, returning the module after the last dot."""
+ __import__(name)
+ return sys.modules[name]
+
+
+class _LazyDescr(object):
+
+ def __init__(self, name):
+ self.name = name
+
+ def __get__(self, obj, tp):
+ result = self._resolve()
+ setattr(obj, self.name, result)
+ # This is a bit ugly, but it avoids running this again.
+ delattr(tp, self.name)
+ return result
+
+
+class MovedModule(_LazyDescr):
+
+ def __init__(self, name, old, new=None):
+ super(MovedModule, self).__init__(name)
+ if PY3:
+ if new is None:
+ new = name
+ self.mod = new
+ else:
+ self.mod = old
+
+ def _resolve(self):
+ return _import_module(self.mod)
+
+
+class MovedAttribute(_LazyDescr):
+
+ def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+ super(MovedAttribute, self).__init__(name)
+ if PY3:
+ if new_mod is None:
+ new_mod = name
+ self.mod = new_mod
+ if new_attr is None:
+ if old_attr is None:
+ new_attr = name
+ else:
+ new_attr = old_attr
+ self.attr = new_attr
+ else:
+ self.mod = old_mod
+ if old_attr is None:
+ old_attr = name
+ self.attr = old_attr
+
+ def _resolve(self):
+ module = _import_module(self.mod)
+ return getattr(module, self.attr)
+
+
+
+class _MovedItems(types.ModuleType):
+ """Lazy loading of moved objects"""
+
+
+_moved_attributes = [
+ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+ MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+ MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+ MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+ MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
+ MovedAttribute("reduce", "__builtin__", "functools"),
+ MovedAttribute("StringIO", "StringIO", "io"),
+ MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+ MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+
+ MovedModule("builtins", "__builtin__"),
+ MovedModule("configparser", "ConfigParser"),
+ MovedModule("copyreg", "copy_reg"),
+ MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+ MovedModule("http_cookies", "Cookie", "http.cookies"),
+ MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+ MovedModule("html_parser", "HTMLParser", "html.parser"),
+ MovedModule("http_client", "httplib", "http.client"),
+ MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+ MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+ MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+ MovedModule("cPickle", "cPickle", "pickle"),
+ MovedModule("queue", "Queue"),
+ MovedModule("reprlib", "repr"),
+ MovedModule("socketserver", "SocketServer"),
+ MovedModule("tkinter", "Tkinter"),
+ MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+ MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+ MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+ MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+ MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+ MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+ MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+ MovedModule("tkinter_colorchooser", "tkColorChooser",
+ "tkinter.colorchooser"),
+ MovedModule("tkinter_commondialog", "tkCommonDialog",
+ "tkinter.commondialog"),
+ MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+ MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+ MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+ MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+ "tkinter.simpledialog"),
+ MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+ MovedModule("winreg", "_winreg"),
+]
+for attr in _moved_attributes:
+ setattr(_MovedItems, attr.name, attr)
+del attr
+
+moves = sys.modules["django.utils.six.moves"] = _MovedItems("moves")
+
+
+def add_move(move):
+ """Add an item to six.moves."""
+ setattr(_MovedItems, move.name, move)
+
+
+def remove_move(name):
+ """Remove item from six.moves."""
+ try:
+ delattr(_MovedItems, name)
+ except AttributeError:
+ try:
+ del moves.__dict__[name]
+ except KeyError:
+ raise AttributeError("no such move, %r" % (name,))
+
+
+if PY3:
+ _meth_func = "__func__"
+ _meth_self = "__self__"
+
+ _func_code = "__code__"
+ _func_defaults = "__defaults__"
+
+ _iterkeys = "keys"
+ _itervalues = "values"
+ _iteritems = "items"
+else:
+ _meth_func = "im_func"
+ _meth_self = "im_self"
+
+ _func_code = "func_code"
+ _func_defaults = "func_defaults"
+
+ _iterkeys = "iterkeys"
+ _itervalues = "itervalues"
+ _iteritems = "iteritems"
+
+
+try:
+ advance_iterator = next
+except NameError:
+ def advance_iterator(it):
+ return it.next()
+next = advance_iterator
+
+
+if PY3:
+ def get_unbound_function(unbound):
+ return unbound
+
+ Iterator = object
+
+ def callable(obj):
+ return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+else:
+ def get_unbound_function(unbound):
+ return unbound.im_func
+
+ class Iterator(object):
+
+ def next(self):
+ return type(self).__next__(self)
+
+ callable = callable
+_add_doc(get_unbound_function,
+ """Get the function out of a possibly unbound function""")
+
+
+get_method_function = operator.attrgetter(_meth_func)
+get_method_self = operator.attrgetter(_meth_self)
+get_function_code = operator.attrgetter(_func_code)
+get_function_defaults = operator.attrgetter(_func_defaults)
+
+
+def iterkeys(d):
+ """Return an iterator over the keys of a dictionary."""
+ return iter(getattr(d, _iterkeys)())
+
+def itervalues(d):
+ """Return an iterator over the values of a dictionary."""
+ return iter(getattr(d, _itervalues)())
+
+def iteritems(d):
+ """Return an iterator over the (key, value) pairs of a dictionary."""
+ return iter(getattr(d, _iteritems)())
+
+
+if PY3:
+ def b(s):
+ return s.encode("latin-1")
+ def u(s):
+ return s
+ if sys.version_info[1] <= 1:
+ def int2byte(i):
+ return bytes((i,))
+ else:
+ # This is about 2x faster than the implementation above on 3.2+
+ int2byte = operator.methodcaller("to_bytes", 1, "big")
+ import io
+ StringIO = io.StringIO
+ BytesIO = io.BytesIO
+else:
+ def b(s):
+ return s
+ def u(s):
+ return unicode(s, "unicode_escape")
+ int2byte = chr
+ import StringIO
+ StringIO = BytesIO = StringIO.StringIO
+_add_doc(b, """Byte literal""")
+_add_doc(u, """Text literal""")
+
+
+if PY3:
+ import builtins
+ exec_ = getattr(builtins, "exec")
+
+
+ def reraise(tp, value, tb=None):
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
+
+
+ print_ = getattr(builtins, "print")
+ del builtins
+
+else:
+ def exec_(code, globs=None, locs=None):
+ """Execute code in a namespace."""
+ if globs is None:
+ frame = sys._getframe(1)
+ globs = frame.f_globals
+ if locs is None:
+ locs = frame.f_locals
+ del frame
+ elif locs is None:
+ locs = globs
+ exec("""exec code in globs, locs""")
+
+
+ exec_("""def reraise(tp, value, tb=None):
+ raise tp, value, tb
+""")
+
+
+ def print_(*args, **kwargs):
+ """The new-style print function."""
+ fp = kwargs.pop("file", sys.stdout)
+ if fp is None:
+ return
+ def write(data):
+ if not isinstance(data, basestring):
+ data = str(data)
+ fp.write(data)
+ want_unicode = False
+ sep = kwargs.pop("sep", None)
+ if sep is not None:
+ if isinstance(sep, unicode):
+ want_unicode = True
+ elif not isinstance(sep, str):
+ raise TypeError("sep must be None or a string")
+ end = kwargs.pop("end", None)
+ if end is not None:
+ if isinstance(end, unicode):
+ want_unicode = True
+ elif not isinstance(end, str):
+ raise TypeError("end must be None or a string")
+ if kwargs:
+ raise TypeError("invalid keyword arguments to print()")
+ if not want_unicode:
+ for arg in args:
+ if isinstance(arg, unicode):
+ want_unicode = True
+ break
+ if want_unicode:
+ newline = unicode("\n")
+ space = unicode(" ")
+ else:
+ newline = "\n"
+ space = " "
+ if sep is None:
+ sep = space
+ if end is None:
+ end = newline
+ for i, arg in enumerate(args):
+ if i:
+ write(sep)
+ write(arg)
+ write(end)
+
+_add_doc(reraise, """Reraise an exception.""")
+
+
+def with_metaclass(meta, base=object):
+ """Create a base class with a metaclass."""
+ return meta("NewBase", (base,), {})
+
+
+### Additional customizations for Django ###
+
+if PY3:
+ _iterlists = "lists"
+ _assertRaisesRegex = "assertRaisesRegex"
+else:
+ _iterlists = "iterlists"
+ _assertRaisesRegex = "assertRaisesRegexp"
+
+
+def iterlists(d):
+ """Return an iterator over the values of a MultiValueDict."""
+ return getattr(d, _iterlists)()
+
+
+def assertRaisesRegex(self, *args, **kwargs):
+ return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+
+
+add_move(MovedModule("_dummy_thread", "dummy_thread"))
+add_move(MovedModule("_thread", "thread"))
diff --git a/rest_framework/status.py b/rest_framework/status.py
index a1eb48da..b9f249f9 100644
--- a/rest_framework/status.py
+++ b/rest_framework/status.py
@@ -4,6 +4,7 @@ Descriptive HTTP status codes, for code readability.
See RFC 2616 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
And RFC 6585 - http://tools.ietf.org/html/rfc6585
"""
+from __future__ import unicode_literals
HTTP_100_CONTINUE = 100
HTTP_101_SWITCHING_PROTOCOLS = 101
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index 82fcdfe7..a1db65bc 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -1,10 +1,12 @@
+from __future__ import unicode_literals, absolute_import
from django import template
from django.core.urlresolvers import reverse
from django.http import QueryDict
-from django.utils.encoding import force_unicode
from django.utils.html import escape
from django.utils.safestring import SafeData, mark_safe
-from urlparse import urlsplit, urlunsplit
+from rest_framework.compat import urlparse
+from rest_framework.compat import force_text
+from rest_framework.compat import six
import re
import string
@@ -99,11 +101,11 @@ def replace_query_param(url, key, val):
Given a URL and a key/val pair, set or replace an item in the query
parameters of the URL, and return the new URL.
"""
- (scheme, netloc, path, query, fragment) = urlsplit(url)
+ (scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
query_dict = QueryDict(query).copy()
query_dict[key] = val
query = query_dict.urlencode()
- return urlunsplit((scheme, netloc, path, query, fragment))
+ return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
# Regex for adding classes to html snippets
@@ -179,7 +181,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 +215,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 +251,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 1f17e8d2..c9df1733 100644
--- a/rest_framework/tests/authentication.py
+++ b/rest_framework/tests/authentication.py
@@ -1,13 +1,13 @@
+from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.http import HttpResponse
from django.test import Client, TestCase
-
+from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import permissions
from rest_framework.authtoken.models import Token
from rest_framework.authentication import TokenAuthentication, BasicAuthentication, SessionAuthentication
from rest_framework.compat import patterns
from rest_framework.views import APIView
-
import json
import base64
@@ -42,13 +42,17 @@ 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()
+ credentials = ('%s:%s' % (self.username, self.password))
+ base64_credentials = base64.b64encode(credentials.encode(HTTP_HEADER_ENCODING)).decode(HTTP_HEADER_ENCODING)
+ auth = 'Basic %s' % base64_credentials
response = self.csrf_client.post('/basic/', {'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()
+ credentials = ('%s:%s' % (self.username, self.password))
+ base64_credentials = base64.b64encode(credentials.encode(HTTP_HEADER_ENCODING)).decode(HTTP_HEADER_ENCODING)
+ auth = 'Basic %s' % base64_credentials
response = self.csrf_client.post('/basic/', json.dumps({'example': 'example'}), 'application/json', HTTP_AUTHORIZATION=auth)
self.assertEqual(response.status_code, 200)
@@ -159,7 +163,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."""
@@ -181,4 +185,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/breadcrumbs.py b/rest_framework/tests/breadcrumbs.py
index df891683..d9ed647e 100644
--- a/rest_framework/tests/breadcrumbs.py
+++ b/rest_framework/tests/breadcrumbs.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.test import TestCase
from rest_framework.compat import patterns, url
from rest_framework.utils.breadcrumbs import get_breadcrumbs
diff --git a/rest_framework/tests/decorators.py b/rest_framework/tests/decorators.py
index 82f912e9..a11af3a5 100644
--- a/rest_framework/tests/decorators.py
+++ b/rest_framework/tests/decorators.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.test import TestCase
from rest_framework import status
from rest_framework.response import Response
diff --git a/rest_framework/tests/description.py b/rest_framework/tests/description.py
index d958b840..20963a9c 100644
--- a/rest_framework/tests/description.py
+++ b/rest_framework/tests/description.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.test import TestCase
from rest_framework.views import APIView
from rest_framework.compat import apply_markdown
diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/fields.py
index 8068272d..b7587bf1 100644
--- a/rest_framework/tests/fields.py
+++ b/rest_framework/tests/fields.py
@@ -1,7 +1,7 @@
"""
General serializer field tests.
"""
-
+from __future__ import unicode_literals
from django.db import models
from django.test import TestCase
from rest_framework import serializers
diff --git a/rest_framework/tests/files.py b/rest_framework/tests/files.py
index 446e23c0..ce00ea6b 100644
--- a/rest_framework/tests/files.py
+++ b/rest_framework/tests/files.py
@@ -1,9 +1,9 @@
-import StringIO
-import datetime
-
+from __future__ import unicode_literals
from django.test import TestCase
-
from rest_framework import serializers
+from rest_framework.compat import BytesIO
+from rest_framework.compat import six
+import datetime
class UploadedFile(object):
@@ -27,9 +27,9 @@ class UploadedFileSerializer(serializers.Serializer):
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/filterset.py b/rest_framework/tests/filterset.py
index af2e6c2e..daea6e53 100644
--- a/rest_framework/tests/filterset.py
+++ b/rest_framework/tests/filterset.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
import datetime
from decimal import Decimal
from django.test import TestCase
diff --git a/rest_framework/tests/genericrelations.py b/rest_framework/tests/genericrelations.py
index 146ad1e4..88d4efa3 100644
--- a/rest_framework/tests/genericrelations.py
+++ b/rest_framework/tests/genericrelations.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.generic import GenericRelation, GenericForeignKey
from django.db import models
@@ -63,8 +64,8 @@ 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)
@@ -84,16 +85,16 @@ class TestGenericRelations(TestCase):
serializer = TagSerializer(Tag.objects.all())
expected = [
{
- 'tag': u'django',
- 'tagged_item': u'Bookmark: https://www.djangoproject.com/'
+ 'tag': 'django',
+ 'tagged_item': 'Bookmark: https://www.djangoproject.com/'
},
{
- 'tag': u'python',
- 'tagged_item': u'Bookmark: https://www.djangoproject.com/'
+ 'tag': 'python',
+ 'tagged_item': 'Bookmark: https://www.djangoproject.com/'
},
{
- 'tag': u'reminder',
- 'tagged_item': u'Note: Remember the milk'
+ 'tag': 'reminder',
+ 'tagged_item': 'Note: Remember the milk'
}
]
self.assertEquals(serializer.data, expected)
diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py
index 4799a04b..fd01312a 100644
--- a/rest_framework/tests/generics.py
+++ b/rest_framework/tests/generics.py
@@ -1,10 +1,11 @@
-import json
+from __future__ import unicode_literals
from django.db import models
from django.test import TestCase
from rest_framework import generics, serializers, status
from rest_framework.tests.utils import RequestFactory
from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel
-
+from rest_framework.compat import six
+import json
factory = RequestFactory()
@@ -72,7 +73,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 +128,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')
@@ -202,7 +203,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..702e8024 100644
--- a/rest_framework/tests/htmlrenderer.py
+++ b/rest_framework/tests/htmlrenderer.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.test import TestCase
@@ -7,6 +8,7 @@ from rest_framework.compat import patterns, url
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.response import Response
+from rest_framework.compat import six
@api_view(('GET',))
@@ -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/hyperlinkedserializers.py b/rest_framework/tests/hyperlinkedserializers.py
index c6a8224b..bc9b8769 100644
--- a/rest_framework/tests/hyperlinkedserializers.py
+++ b/rest_framework/tests/hyperlinkedserializers.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
import json
from django.test import TestCase
from django.test.client import RequestFactory
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index 9ab15328..f2117538 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -1,35 +1,6 @@
+from __future__ import unicode_literals
from django.db import models
-from django.contrib.contenttypes.models import ContentType
-from django.contrib.contenttypes.generic import GenericForeignKey, GenericRelation
-# from django.contrib.auth.models import Group
-
-
-# class CustomUser(models.Model):
-# """
-# A custom user model, which uses a 'through' table for the foreign key
-# """
-# username = models.CharField(max_length=255, unique=True)
-# groups = models.ManyToManyField(
-# to=Group, blank=True, null=True, through='UserGroupMap'
-# )
-
-# @models.permalink
-# def get_absolute_url(self):
-# return ('custom_user', (), {
-# 'pk': self.id
-# })
-
-
-# class UserGroupMap(models.Model):
-# user = models.ForeignKey(to=CustomUser)
-# group = models.ForeignKey(to=Group)
-
-# @models.permalink
-# def get_absolute_url(self):
-# return ('user_group_map', (), {
-# 'pk': self.id
-# })
def foobar():
return 'foobar'
diff --git a/rest_framework/tests/modelviews.py b/rest_framework/tests/modelviews.py
deleted file mode 100644
index f12e3b97..00000000
--- a/rest_framework/tests/modelviews.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# from rest_framework.compat import patterns, url
-# from django.forms import ModelForm
-# from django.contrib.auth.models import Group, User
-# from rest_framework.resources import ModelResource
-# from rest_framework.views import ListOrCreateModelView, InstanceModelView
-# from rest_framework.tests.models import CustomUser
-# from rest_framework.tests.testcases import TestModelsTestCase
-
-
-# class GroupResource(ModelResource):
-# model = Group
-
-
-# class UserForm(ModelForm):
-# class Meta:
-# model = User
-# exclude = ('last_login', 'date_joined')
-
-
-# class UserResource(ModelResource):
-# model = User
-# form = UserForm
-
-
-# class CustomUserResource(ModelResource):
-# model = CustomUser
-
-# urlpatterns = patterns('',
-# url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'),
-# url(r'^users/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=UserResource)),
-# url(r'^customusers/$', ListOrCreateModelView.as_view(resource=CustomUserResource), name='customusers'),
-# url(r'^customusers/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=CustomUserResource)),
-# url(r'^groups/$', ListOrCreateModelView.as_view(resource=GroupResource), name='groups'),
-# url(r'^groups/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=GroupResource)),
-# )
-
-
-# class ModelViewTests(TestModelsTestCase):
-# """Test the model views rest_framework provides"""
-# urls = 'rest_framework.tests.modelviews'
-
-# def test_creation(self):
-# """Ensure that a model object can be created"""
-# self.assertEqual(0, Group.objects.count())
-
-# response = self.client.post('/groups/', {'name': 'foo'})
-
-# self.assertEqual(response.status_code, 201)
-# self.assertEqual(1, Group.objects.count())
-# self.assertEqual('foo', Group.objects.all()[0].name)
-
-# def test_creation_with_m2m_relation(self):
-# """Ensure that a model object with a m2m relation can be created"""
-# group = Group(name='foo')
-# group.save()
-# self.assertEqual(0, User.objects.count())
-
-# response = self.client.post('/users/', {'username': 'bar', 'password': 'baz', 'groups': [group.id]})
-
-# self.assertEqual(response.status_code, 201)
-# self.assertEqual(1, User.objects.count())
-
-# user = User.objects.all()[0]
-# self.assertEqual('bar', user.username)
-# self.assertEqual('baz', user.password)
-# self.assertEqual(1, user.groups.count())
-
-# group = user.groups.all()[0]
-# self.assertEqual('foo', group.name)
-
-# def test_creation_with_m2m_relation_through(self):
-# """
-# Ensure that a model object with a m2m relation can be created where that
-# relation uses a through table
-# """
-# group = Group(name='foo')
-# group.save()
-# self.assertEqual(0, User.objects.count())
-
-# response = self.client.post('/customusers/', {'username': 'bar', 'groups': [group.id]})
-
-# self.assertEqual(response.status_code, 201)
-# self.assertEqual(1, CustomUser.objects.count())
-
-# user = CustomUser.objects.all()[0]
-# self.assertEqual('bar', user.username)
-# self.assertEqual(1, user.groups.count())
-
-# group = user.groups.all()[0]
-# self.assertEqual('foo', group.name)
diff --git a/rest_framework/tests/negotiation.py b/rest_framework/tests/negotiation.py
index e06354ea..5769dd5f 100644
--- a/rest_framework/tests/negotiation.py
+++ b/rest_framework/tests/negotiation.py
@@ -1,6 +1,9 @@
+from __future__ import unicode_literals
from django.test import TestCase
from django.test.client import RequestFactory
from rest_framework.negotiation import DefaultContentNegotiation
+from rest_framework.request import Request
+
factory = RequestFactory()
@@ -22,16 +25,16 @@ class TestAcceptedMediaType(TestCase):
return self.negotiator.select_renderer(request, self.renderers)
def test_client_without_accept_use_renderer(self):
- request = factory.get('/')
+ request = Request(factory.get('/'))
accepted_renderer, accepted_media_type = self.select_renderer(request)
self.assertEquals(accepted_media_type, 'application/json')
def test_client_underspecifies_accept_use_renderer(self):
- request = factory.get('/', HTTP_ACCEPT='*/*')
+ request = Request(factory.get('/', HTTP_ACCEPT='*/*'))
accepted_renderer, accepted_media_type = self.select_renderer(request)
self.assertEquals(accepted_media_type, 'application/json')
def test_client_overspecifies_accept_use_client(self):
- request = factory.get('/', HTTP_ACCEPT='application/json; indent=8')
+ request = Request(factory.get('/', HTTP_ACCEPT='application/json; indent=8'))
accepted_renderer, accepted_media_type = self.select_renderer(request)
self.assertEquals(accepted_media_type, 'application/json; indent=8')
diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py
index 697dfb5b..b85ce144 100644
--- a/rest_framework/tests/pagination.py
+++ b/rest_framework/tests/pagination.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
import datetime
from decimal import Decimal
from django.core.paginator import Paginator
diff --git a/rest_framework/tests/parsers.py b/rest_framework/tests/parsers.py
index 8ab8a52f..c03df08f 100644
--- a/rest_framework/tests/parsers.py
+++ b/rest_framework/tests/parsers.py
@@ -1,137 +1,5 @@
-# """
-# ..
-# >>> from rest_framework.parsers import FormParser
-# >>> from django.test.client import RequestFactory
-# >>> from rest_framework.views import View
-# >>> from StringIO import StringIO
-# >>> from urllib import urlencode
-# >>> req = RequestFactory().get('/')
-# >>> some_view = View()
-# >>> some_view.request = req # Make as if this request had been dispatched
-#
-# FormParser
-# ============
-#
-# Data flatening
-# ----------------
-#
-# Here is some example data, which would eventually be sent along with a post request :
-#
-# >>> inpt = urlencode([
-# ... ('key1', 'bla1'),
-# ... ('key2', 'blo1'), ('key2', 'blo2'),
-# ... ])
-#
-# Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter :
-#
-# >>> (data, files) = FormParser(some_view).parse(StringIO(inpt))
-# >>> data == {'key1': 'bla1', 'key2': 'blo1'}
-# True
-#
-# However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` :
-#
-# >>> class MyFormParser(FormParser):
-# ...
-# ... def is_a_list(self, key, val_list):
-# ... return len(val_list) > 1
-#
-# This new parser only flattens the lists of parameters that contain a single value.
-#
-# >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
-# >>> data == {'key1': 'bla1', 'key2': ['blo1', 'blo2']}
-# True
-#
-# .. note:: The same functionality is available for :class:`parsers.MultiPartParser`.
-#
-# Submitting an empty list
-# --------------------------
-#
-# When submitting an empty select multiple, like this one ::
-#
-# <select multiple="multiple" name="key2"></select>
-#
-# The browsers usually strip the parameter completely. A hack to avoid this, and therefore being able to submit an empty select multiple, is to submit a value that tells the server that the list is empty ::
-#
-# <select multiple="multiple" name="key2"><option value="_empty"></select>
-#
-# :class:`parsers.FormParser` provides the server-side implementation for this hack. Considering the following posted data :
-#
-# >>> inpt = urlencode([
-# ... ('key1', 'blo1'), ('key1', '_empty'),
-# ... ('key2', '_empty'),
-# ... ])
-#
-# :class:`parsers.FormParser` strips the values ``_empty`` from all the lists.
-#
-# >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
-# >>> data == {'key1': 'blo1'}
-# True
-#
-# Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it.
-#
-# >>> class MyFormParser(FormParser):
-# ...
-# ... def is_a_list(self, key, val_list):
-# ... return key == 'key2'
-# ...
-# >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
-# >>> data == {'key1': 'blo1', 'key2': []}
-# True
-#
-# Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`.
-# """
-# import httplib, mimetypes
-# from tempfile import TemporaryFile
-# from django.test import TestCase
-# from django.test.client import RequestFactory
-# from rest_framework.parsers import MultiPartParser
-# from rest_framework.views import View
-# from StringIO import StringIO
-#
-# def encode_multipart_formdata(fields, files):
-# """For testing multipart parser.
-# fields is a sequence of (name, value) elements for regular form fields.
-# files is a sequence of (name, filename, value) elements for data to be uploaded as files
-# Return (content_type, body)."""
-# BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
-# CRLF = '\r\n'
-# L = []
-# for (key, value) in fields:
-# L.append('--' + BOUNDARY)
-# L.append('Content-Disposition: form-data; name="%s"' % key)
-# L.append('')
-# L.append(value)
-# for (key, filename, value) in files:
-# L.append('--' + BOUNDARY)
-# L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
-# L.append('Content-Type: %s' % get_content_type(filename))
-# L.append('')
-# L.append(value)
-# L.append('--' + BOUNDARY + '--')
-# L.append('')
-# body = CRLF.join(L)
-# content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
-# return content_type, body
-#
-# def get_content_type(filename):
-# return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
-#
-#class TestMultiPartParser(TestCase):
-# def setUp(self):
-# self.req = RequestFactory()
-# self.content_type, self.body = encode_multipart_formdata([('key1', 'val1'), ('key1', 'val2')],
-# [('file1', 'pic.jpg', 'blablabla'), ('file1', 't.txt', 'blobloblo')])
-#
-# def test_multipartparser(self):
-# """Ensure that MultiPartParser can parse multipart/form-data that contains a mix of several files and parameters."""
-# post_req = RequestFactory().post('/', self.body, content_type=self.content_type)
-# view = View()
-# view.request = post_req
-# (data, files) = MultiPartParser(view).parse(StringIO(self.body))
-# self.assertEqual(data['key1'], 'val1')
-# self.assertEqual(files['file1'].read(), 'blablabla')
-
-from StringIO import StringIO
+from __future__ import unicode_literals
+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/relations.py b/rest_framework/tests/relations.py
index edc85f9e..5fc32038 100644
--- a/rest_framework/tests/relations.py
+++ b/rest_framework/tests/relations.py
@@ -1,7 +1,7 @@
"""
General tests for relational fields.
"""
-
+from __future__ import unicode_literals
from django.db import models
from django.test import TestCase
from rest_framework import serializers
diff --git a/rest_framework/tests/relations_hyperlink.py b/rest_framework/tests/relations_hyperlink.py
index 6d137f68..4fbf0b63 100644
--- a/rest_framework/tests/relations_hyperlink.py
+++ b/rest_framework/tests/relations_hyperlink.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.test import TestCase
from rest_framework import serializers
from rest_framework.compat import patterns, url
@@ -74,9 +75,9 @@ class HyperlinkedManyToManyTests(TestCase):
queryset = ManyToManySource.objects.all()
serializer = ManyToManySourceSerializer(queryset)
expected = [
- {'url': '/manytomanysource/1/', 'name': u'source-1', 'targets': ['/manytomanytarget/1/']},
- {'url': '/manytomanysource/2/', 'name': u'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']},
- {'url': '/manytomanysource/3/', 'name': u'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']}
+ {'url': '/manytomanysource/1/', 'name': 'source-1', 'targets': ['/manytomanytarget/1/']},
+ {'url': '/manytomanysource/2/', 'name': 'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']},
+ {'url': '/manytomanysource/3/', 'name': 'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']}
]
self.assertEquals(serializer.data, expected)
@@ -84,14 +85,14 @@ class HyperlinkedManyToManyTests(TestCase):
queryset = ManyToManyTarget.objects.all()
serializer = ManyToManyTargetSerializer(queryset)
expected = [
- {'url': '/manytomanytarget/1/', 'name': u'target-1', 'sources': ['/manytomanysource/1/', '/manytomanysource/2/', '/manytomanysource/3/']},
- {'url': '/manytomanytarget/2/', 'name': u'target-2', 'sources': ['/manytomanysource/2/', '/manytomanysource/3/']},
- {'url': '/manytomanytarget/3/', 'name': u'target-3', 'sources': ['/manytomanysource/3/']}
+ {'url': '/manytomanytarget/1/', 'name': 'target-1', 'sources': ['/manytomanysource/1/', '/manytomanysource/2/', '/manytomanysource/3/']},
+ {'url': '/manytomanytarget/2/', 'name': 'target-2', 'sources': ['/manytomanysource/2/', '/manytomanysource/3/']},
+ {'url': '/manytomanytarget/3/', 'name': 'target-3', 'sources': ['/manytomanysource/3/']}
]
self.assertEquals(serializer.data, expected)
def test_many_to_many_update(self):
- data = {'url': '/manytomanysource/1/', 'name': u'source-1', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']}
+ data = {'url': '/manytomanysource/1/', 'name': 'source-1', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']}
instance = ManyToManySource.objects.get(pk=1)
serializer = ManyToManySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -102,14 +103,14 @@ class HyperlinkedManyToManyTests(TestCase):
queryset = ManyToManySource.objects.all()
serializer = ManyToManySourceSerializer(queryset)
expected = [
- {'url': '/manytomanysource/1/', 'name': u'source-1', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']},
- {'url': '/manytomanysource/2/', 'name': u'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']},
- {'url': '/manytomanysource/3/', 'name': u'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']}
+ {'url': '/manytomanysource/1/', 'name': 'source-1', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']},
+ {'url': '/manytomanysource/2/', 'name': 'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']},
+ {'url': '/manytomanysource/3/', 'name': 'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']}
]
self.assertEquals(serializer.data, expected)
def test_reverse_many_to_many_update(self):
- data = {'url': '/manytomanytarget/1/', 'name': u'target-1', 'sources': ['/manytomanysource/1/']}
+ data = {'url': '/manytomanytarget/1/', 'name': 'target-1', 'sources': ['/manytomanysource/1/']}
instance = ManyToManyTarget.objects.get(pk=1)
serializer = ManyToManyTargetSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -120,48 +121,48 @@ class HyperlinkedManyToManyTests(TestCase):
queryset = ManyToManyTarget.objects.all()
serializer = ManyToManyTargetSerializer(queryset)
expected = [
- {'url': '/manytomanytarget/1/', 'name': u'target-1', 'sources': ['/manytomanysource/1/']},
- {'url': '/manytomanytarget/2/', 'name': u'target-2', 'sources': ['/manytomanysource/2/', '/manytomanysource/3/']},
- {'url': '/manytomanytarget/3/', 'name': u'target-3', 'sources': ['/manytomanysource/3/']}
+ {'url': '/manytomanytarget/1/', 'name': 'target-1', 'sources': ['/manytomanysource/1/']},
+ {'url': '/manytomanytarget/2/', 'name': 'target-2', 'sources': ['/manytomanysource/2/', '/manytomanysource/3/']},
+ {'url': '/manytomanytarget/3/', 'name': 'target-3', 'sources': ['/manytomanysource/3/']}
]
self.assertEquals(serializer.data, expected)
def test_many_to_many_create(self):
- data = {'url': '/manytomanysource/4/', 'name': u'source-4', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/3/']}
+ data = {'url': '/manytomanysource/4/', 'name': 'source-4', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/3/']}
serializer = ManyToManySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is added, and everything else is as expected
queryset = ManyToManySource.objects.all()
serializer = ManyToManySourceSerializer(queryset)
expected = [
- {'url': '/manytomanysource/1/', 'name': u'source-1', 'targets': ['/manytomanytarget/1/']},
- {'url': '/manytomanysource/2/', 'name': u'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']},
- {'url': '/manytomanysource/3/', 'name': u'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']},
- {'url': '/manytomanysource/4/', 'name': u'source-4', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/3/']}
+ {'url': '/manytomanysource/1/', 'name': 'source-1', 'targets': ['/manytomanytarget/1/']},
+ {'url': '/manytomanysource/2/', 'name': 'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']},
+ {'url': '/manytomanysource/3/', 'name': 'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']},
+ {'url': '/manytomanysource/4/', 'name': 'source-4', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/3/']}
]
self.assertEquals(serializer.data, expected)
def test_reverse_many_to_many_create(self):
- data = {'url': '/manytomanytarget/4/', 'name': u'target-4', 'sources': ['/manytomanysource/1/', '/manytomanysource/3/']}
+ data = {'url': '/manytomanytarget/4/', 'name': 'target-4', 'sources': ['/manytomanysource/1/', '/manytomanysource/3/']}
serializer = ManyToManyTargetSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'target-4')
+ 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 = [
- {'url': '/manytomanytarget/1/', 'name': u'target-1', 'sources': ['/manytomanysource/1/', '/manytomanysource/2/', '/manytomanysource/3/']},
- {'url': '/manytomanytarget/2/', 'name': u'target-2', 'sources': ['/manytomanysource/2/', '/manytomanysource/3/']},
- {'url': '/manytomanytarget/3/', 'name': u'target-3', 'sources': ['/manytomanysource/3/']},
- {'url': '/manytomanytarget/4/', 'name': u'target-4', 'sources': ['/manytomanysource/1/', '/manytomanysource/3/']}
+ {'url': '/manytomanytarget/1/', 'name': 'target-1', 'sources': ['/manytomanysource/1/', '/manytomanysource/2/', '/manytomanysource/3/']},
+ {'url': '/manytomanytarget/2/', 'name': 'target-2', 'sources': ['/manytomanysource/2/', '/manytomanysource/3/']},
+ {'url': '/manytomanytarget/3/', 'name': 'target-3', 'sources': ['/manytomanysource/3/']},
+ {'url': '/manytomanytarget/4/', 'name': 'target-4', 'sources': ['/manytomanysource/1/', '/manytomanysource/3/']}
]
self.assertEquals(serializer.data, expected)
@@ -182,9 +183,9 @@ class HyperlinkedForeignKeyTests(TestCase):
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/1/'},
- {'url': '/foreignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
- {'url': '/foreignkeysource/3/', 'name': u'source-3', 'target': '/foreignkeytarget/1/'}
+ {'url': '/foreignkeysource/1/', 'name': 'source-1', 'target': '/foreignkeytarget/1/'},
+ {'url': '/foreignkeysource/2/', 'name': 'source-2', 'target': '/foreignkeytarget/1/'},
+ {'url': '/foreignkeysource/3/', 'name': 'source-3', 'target': '/foreignkeytarget/1/'}
]
self.assertEquals(serializer.data, expected)
@@ -192,13 +193,13 @@ class HyperlinkedForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'url': '/foreignkeytarget/1/', 'name': u'target-1', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/2/', '/foreignkeysource/3/']},
- {'url': '/foreignkeytarget/2/', 'name': u'target-2', 'sources': []},
+ {'url': '/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/2/', '/foreignkeysource/3/']},
+ {'url': '/foreignkeytarget/2/', 'name': 'target-2', 'sources': []},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update(self):
- data = {'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/2/'}
+ data = {'url': '/foreignkeysource/1/', 'name': 'source-1', 'target': '/foreignkeytarget/2/'}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -209,21 +210,21 @@ class HyperlinkedForeignKeyTests(TestCase):
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/2/'},
- {'url': '/foreignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
- {'url': '/foreignkeysource/3/', 'name': u'source-3', 'target': '/foreignkeytarget/1/'}
+ {'url': '/foreignkeysource/1/', 'name': 'source-1', 'target': '/foreignkeytarget/2/'},
+ {'url': '/foreignkeysource/2/', 'name': 'source-2', 'target': '/foreignkeytarget/1/'},
+ {'url': '/foreignkeysource/3/', 'name': 'source-3', 'target': '/foreignkeytarget/1/'}
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update_incorrect_type(self):
- data = {'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': 2}
+ data = {'url': '/foreignkeysource/1/', 'name': 'source-1', 'target': 2}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'target': [u'Incorrect type. Expected url string, received int.']})
+ self.assertEquals(serializer.errors, {'target': ['Incorrect type. Expected url string, received int.']})
def test_reverse_foreign_key_update(self):
- data = {'url': '/foreignkeytarget/2/', 'name': u'target-2', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/3/']}
+ data = {'url': '/foreignkeytarget/2/', 'name': 'target-2', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/3/']}
instance = ForeignKeyTarget.objects.get(pk=2)
serializer = ForeignKeyTargetSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -232,8 +233,8 @@ class HyperlinkedForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
new_serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'url': '/foreignkeytarget/1/', 'name': u'target-1', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/2/', '/foreignkeysource/3/']},
- {'url': '/foreignkeytarget/2/', 'name': u'target-2', 'sources': []},
+ {'url': '/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/2/', '/foreignkeysource/3/']},
+ {'url': '/foreignkeytarget/2/', 'name': 'target-2', 'sources': []},
]
self.assertEquals(new_serializer.data, expected)
@@ -244,54 +245,54 @@ class HyperlinkedForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'url': '/foreignkeytarget/1/', 'name': u'target-1', 'sources': ['/foreignkeysource/2/']},
- {'url': '/foreignkeytarget/2/', 'name': u'target-2', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/3/']},
+ {'url': '/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['/foreignkeysource/2/']},
+ {'url': '/foreignkeytarget/2/', 'name': 'target-2', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/3/']},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_create(self):
- data = {'url': '/foreignkeysource/4/', 'name': u'source-4', 'target': '/foreignkeytarget/2/'}
+ data = {'url': '/foreignkeysource/4/', 'name': 'source-4', 'target': '/foreignkeytarget/2/'}
serializer = ForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 1 is updated, and everything else is as expected
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/1/'},
- {'url': '/foreignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
- {'url': '/foreignkeysource/3/', 'name': u'source-3', 'target': '/foreignkeytarget/1/'},
- {'url': '/foreignkeysource/4/', 'name': u'source-4', 'target': '/foreignkeytarget/2/'},
+ {'url': '/foreignkeysource/1/', 'name': 'source-1', 'target': '/foreignkeytarget/1/'},
+ {'url': '/foreignkeysource/2/', 'name': 'source-2', 'target': '/foreignkeytarget/1/'},
+ {'url': '/foreignkeysource/3/', 'name': 'source-3', 'target': '/foreignkeytarget/1/'},
+ {'url': '/foreignkeysource/4/', 'name': 'source-4', 'target': '/foreignkeytarget/2/'},
]
self.assertEquals(serializer.data, expected)
def test_reverse_foreign_key_create(self):
- data = {'url': '/foreignkeytarget/3/', 'name': u'target-3', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/3/']}
+ data = {'url': '/foreignkeytarget/3/', 'name': 'target-3', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/3/']}
serializer = ForeignKeyTargetSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'target-3')
+ self.assertEqual(obj.name, 'target-3')
# Ensure target 4 is added, and everything else is as expected
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'url': '/foreignkeytarget/1/', 'name': u'target-1', 'sources': ['/foreignkeysource/2/']},
- {'url': '/foreignkeytarget/2/', 'name': u'target-2', 'sources': []},
- {'url': '/foreignkeytarget/3/', 'name': u'target-3', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/3/']},
+ {'url': '/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['/foreignkeysource/2/']},
+ {'url': '/foreignkeytarget/2/', 'name': 'target-2', 'sources': []},
+ {'url': '/foreignkeytarget/3/', 'name': 'target-3', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/3/']},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update_with_invalid_null(self):
- data = {'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': None}
+ data = {'url': '/foreignkeysource/1/', 'name': 'source-1', 'target': None}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'target': [u'Value may not be null']})
+ self.assertEquals(serializer.errors, {'target': ['This field is required.']})
class HyperlinkedNullableForeignKeyTests(TestCase):
@@ -310,28 +311,28 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/1/'},
- {'url': '/nullableforeignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
- {'url': '/nullableforeignkeysource/3/', 'name': u'source-3', 'target': None},
+ {'url': '/nullableforeignkeysource/1/', 'name': 'source-1', 'target': '/foreignkeytarget/1/'},
+ {'url': '/nullableforeignkeysource/2/', 'name': 'source-2', 'target': '/foreignkeytarget/1/'},
+ {'url': '/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_create_with_valid_null(self):
- data = {'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': None}
+ data = {'url': '/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None}
serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/1/'},
- {'url': '/nullableforeignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
- {'url': '/nullableforeignkeysource/3/', 'name': u'source-3', 'target': None},
- {'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': None}
+ {'url': '/nullableforeignkeysource/1/', 'name': 'source-1', 'target': '/foreignkeytarget/1/'},
+ {'url': '/nullableforeignkeysource/2/', 'name': 'source-2', 'target': '/foreignkeytarget/1/'},
+ {'url': '/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None},
+ {'url': '/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None}
]
self.assertEquals(serializer.data, expected)
@@ -340,27 +341,27 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
The emptystring should be interpreted as null in the context
of relationships.
"""
- data = {'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': ''}
- expected_data = {'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': None}
+ data = {'url': '/nullableforeignkeysource/4/', 'name': 'source-4', 'target': ''}
+ expected_data = {'url': '/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None}
serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, expected_data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/1/'},
- {'url': '/nullableforeignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
- {'url': '/nullableforeignkeysource/3/', 'name': u'source-3', 'target': None},
- {'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': None}
+ {'url': '/nullableforeignkeysource/1/', 'name': 'source-1', 'target': '/foreignkeytarget/1/'},
+ {'url': '/nullableforeignkeysource/2/', 'name': 'source-2', 'target': '/foreignkeytarget/1/'},
+ {'url': '/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None},
+ {'url': '/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None}
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update_with_valid_null(self):
- data = {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': None}
+ data = {'url': '/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -371,9 +372,9 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': None},
- {'url': '/nullableforeignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
- {'url': '/nullableforeignkeysource/3/', 'name': u'source-3', 'target': None},
+ {'url': '/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None},
+ {'url': '/nullableforeignkeysource/2/', 'name': 'source-2', 'target': '/foreignkeytarget/1/'},
+ {'url': '/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None},
]
self.assertEquals(serializer.data, expected)
@@ -382,8 +383,8 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
The emptystring should be interpreted as null in the context
of relationships.
"""
- data = {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': ''}
- expected_data = {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': None}
+ data = {'url': '/nullableforeignkeysource/1/', 'name': 'source-1', 'target': ''}
+ expected_data = {'url': '/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -394,9 +395,9 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': None},
- {'url': '/nullableforeignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
- {'url': '/nullableforeignkeysource/3/', 'name': u'source-3', 'target': None},
+ {'url': '/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None},
+ {'url': '/nullableforeignkeysource/2/', 'name': 'source-2', 'target': '/foreignkeytarget/1/'},
+ {'url': '/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None},
]
self.assertEquals(serializer.data, expected)
@@ -405,7 +406,7 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
# and cannot be arbitrarily set.
# def test_reverse_foreign_key_update(self):
- # data = {'id': 1, 'name': u'target-1', 'sources': [1]}
+ # data = {'id': 1, 'name': 'target-1', 'sources': [1]}
# instance = ForeignKeyTarget.objects.get(pk=1)
# serializer = ForeignKeyTargetSerializer(instance, data=data)
# self.assertTrue(serializer.is_valid())
@@ -416,8 +417,8 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
# queryset = ForeignKeyTarget.objects.all()
# serializer = ForeignKeyTargetSerializer(queryset)
# expected = [
- # {'id': 1, 'name': u'target-1', 'sources': [1]},
- # {'id': 2, 'name': u'target-2', 'sources': []},
+ # {'id': 1, 'name': 'target-1', 'sources': [1]},
+ # {'id': 2, 'name': 'target-2', 'sources': []},
# ]
# self.assertEquals(serializer.data, expected)
@@ -437,7 +438,7 @@ class HyperlinkedNullableOneToOneTests(TestCase):
queryset = OneToOneTarget.objects.all()
serializer = NullableOneToOneTargetSerializer(queryset)
expected = [
- {'url': '/onetoonetarget/1/', 'name': u'target-1', 'nullable_source': '/nullableonetoonesource/1/'},
- {'url': '/onetoonetarget/2/', 'name': u'target-2', 'nullable_source': None},
+ {'url': '/onetoonetarget/1/', 'name': 'target-1', 'nullable_source': '/nullableonetoonesource/1/'},
+ {'url': '/onetoonetarget/2/', 'name': 'target-2', 'nullable_source': None},
]
self.assertEquals(serializer.data, expected)
diff --git a/rest_framework/tests/relations_nested.py b/rest_framework/tests/relations_nested.py
index 0e129fae..e9051e71 100644
--- a/rest_framework/tests/relations_nested.py
+++ b/rest_framework/tests/relations_nested.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.test import TestCase
from rest_framework import serializers
from rest_framework.tests.models import ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource
@@ -53,9 +54,9 @@ class ReverseForeignKeyTests(TestCase):
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': {'id': 1, 'name': u'target-1'}},
- {'id': 2, 'name': u'source-2', 'target': {'id': 1, 'name': u'target-1'}},
- {'id': 3, 'name': u'source-3', 'target': {'id': 1, 'name': u'target-1'}},
+ {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}},
+ {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}},
+ {'id': 3, 'name': 'source-3', 'target': {'id': 1, 'name': 'target-1'}},
]
self.assertEquals(serializer.data, expected)
@@ -63,12 +64,12 @@ class ReverseForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': [
- {'id': 1, 'name': u'source-1', 'target': 1},
- {'id': 2, 'name': u'source-2', 'target': 1},
- {'id': 3, 'name': u'source-3', 'target': 1},
+ {'id': 1, 'name': 'target-1', 'sources': [
+ {'id': 1, 'name': 'source-1', 'target': 1},
+ {'id': 2, 'name': 'source-2', 'target': 1},
+ {'id': 3, 'name': 'source-3', 'target': 1},
]},
- {'id': 2, 'name': u'target-2', 'sources': [
+ {'id': 2, 'name': 'target-2', 'sources': [
]}
]
self.assertEquals(serializer.data, expected)
@@ -88,9 +89,9 @@ class NestedNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': {'id': 1, 'name': u'target-1'}},
- {'id': 2, 'name': u'source-2', 'target': {'id': 1, 'name': u'target-1'}},
- {'id': 3, 'name': u'source-3', 'target': None},
+ {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}},
+ {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}},
+ {'id': 3, 'name': 'source-3', 'target': None},
]
self.assertEquals(serializer.data, expected)
@@ -108,7 +109,7 @@ class NestedNullableOneToOneTests(TestCase):
queryset = OneToOneTarget.objects.all()
serializer = NullableOneToOneTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'nullable_source': {'id': 1, 'name': u'source-1', 'target': 1}},
- {'id': 2, 'name': u'target-2', 'nullable_source': None},
+ {'id': 1, 'name': 'target-1', 'nullable_source': {'id': 1, 'name': 'source-1', 'target': 1}},
+ {'id': 2, 'name': 'target-2', 'nullable_source': None},
]
self.assertEquals(serializer.data, expected)
diff --git a/rest_framework/tests/relations_pk.py b/rest_framework/tests/relations_pk.py
index 3391e60a..ffd1127e 100644
--- a/rest_framework/tests/relations_pk.py
+++ b/rest_framework/tests/relations_pk.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.test import TestCase
from rest_framework import serializers
from rest_framework.tests.models import ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource
@@ -56,9 +57,9 @@ class PKManyToManyTests(TestCase):
queryset = ManyToManySource.objects.all()
serializer = ManyToManySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'targets': [1]},
- {'id': 2, 'name': u'source-2', 'targets': [1, 2]},
- {'id': 3, 'name': u'source-3', 'targets': [1, 2, 3]}
+ {'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)
@@ -66,14 +67,14 @@ class PKManyToManyTests(TestCase):
queryset = ManyToManyTarget.objects.all()
serializer = ManyToManyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]},
- {'id': 2, 'name': u'target-2', 'sources': [2, 3]},
- {'id': 3, 'name': u'target-3', 'sources': [3]}
+ {'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': u'source-1', 'targets': [1, 2, 3]}
+ 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())
@@ -84,14 +85,14 @@ class PKManyToManyTests(TestCase):
queryset = ManyToManySource.objects.all()
serializer = ManyToManySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'targets': [1, 2, 3]},
- {'id': 2, 'name': u'source-2', 'targets': [1, 2]},
- {'id': 3, 'name': u'source-3', 'targets': [1, 2, 3]}
+ {'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': u'target-1', 'sources': [1]}
+ data = {'id': 1, 'name': 'target-1', 'sources': [1]}
instance = ManyToManyTarget.objects.get(pk=1)
serializer = ManyToManyTargetSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -102,47 +103,47 @@ class PKManyToManyTests(TestCase):
queryset = ManyToManyTarget.objects.all()
serializer = ManyToManyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': [1]},
- {'id': 2, 'name': u'target-2', 'sources': [2, 3]},
- {'id': 3, 'name': u'target-3', 'sources': [3]}
+ {'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_many_to_many_create(self):
- data = {'id': 4, 'name': u'source-4', 'targets': [1, 3]}
+ data = {'id': 4, 'name': 'source-4', 'targets': [1, 3]}
serializer = ManyToManySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is added, and everything else is as expected
queryset = ManyToManySource.objects.all()
serializer = ManyToManySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'targets': [1]},
- {'id': 2, 'name': u'source-2', 'targets': [1, 2]},
- {'id': 3, 'name': u'source-3', 'targets': [1, 2, 3]},
- {'id': 4, 'name': u'source-4', 'targets': [1, 3]},
+ {'id': 1, 'name': 'source-1', 'targets': [1]},
+ {'id': 2, 'name': 'source-2', 'targets': [1, 2]},
+ {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]},
+ {'id': 4, 'name': 'source-4', 'targets': [1, 3]},
]
self.assertEquals(serializer.data, expected)
def test_reverse_many_to_many_create(self):
- data = {'id': 4, 'name': u'target-4', 'sources': [1, 3]}
+ 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, u'target-4')
+ 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': u'target-1', 'sources': [1, 2, 3]},
- {'id': 2, 'name': u'target-2', 'sources': [2, 3]},
- {'id': 3, 'name': u'target-3', 'sources': [3]},
- {'id': 4, 'name': u'target-4', 'sources': [1, 3]}
+ {'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)
@@ -161,9 +162,9 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 1},
- {'id': 2, 'name': u'source-2', 'target': 1},
- {'id': 3, 'name': u'source-3', 'target': 1}
+ {'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)
@@ -171,13 +172,13 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]},
- {'id': 2, 'name': u'target-2', 'sources': []},
+ {'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': u'source-1', 'target': 2}
+ data = {'id': 1, 'name': 'source-1', 'target': 2}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -188,21 +189,21 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 2},
- {'id': 2, 'name': u'source-2', 'target': 1},
- {'id': 3, 'name': u'source-3', 'target': 1}
+ {'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)
def test_foreign_key_update_incorrect_type(self):
- data = {'id': 1, 'name': u'source-1', 'target': 'foo'}
+ data = {'id': 1, 'name': 'source-1', 'target': 'foo'}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'target': [u'Incorrect type. Expected pk value, received str.']})
+ self.assertEquals(serializer.errors, {'target': ['Incorrect type. Expected pk value, received str.']})
def test_reverse_foreign_key_update(self):
- data = {'id': 2, 'name': u'target-2', 'sources': [1, 3]}
+ data = {'id': 2, 'name': 'target-2', 'sources': [1, 3]}
instance = ForeignKeyTarget.objects.get(pk=2)
serializer = ForeignKeyTargetSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -211,8 +212,8 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
new_serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]},
- {'id': 2, 'name': u'target-2', 'sources': []},
+ {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]},
+ {'id': 2, 'name': 'target-2', 'sources': []},
]
self.assertEquals(new_serializer.data, expected)
@@ -223,54 +224,54 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': [2]},
- {'id': 2, 'name': u'target-2', 'sources': [1, 3]},
+ {'id': 1, 'name': 'target-1', 'sources': [2]},
+ {'id': 2, 'name': 'target-2', 'sources': [1, 3]},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_create(self):
- data = {'id': 4, 'name': u'source-4', 'target': 2}
+ data = {'id': 4, 'name': 'source-4', 'target': 2}
serializer = ForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is added, and everything else is as expected
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 1},
- {'id': 2, 'name': u'source-2', 'target': 1},
- {'id': 3, 'name': u'source-3', 'target': 1},
- {'id': 4, 'name': u'source-4', 'target': 2},
+ {'id': 1, 'name': 'source-1', 'target': 1},
+ {'id': 2, 'name': 'source-2', 'target': 1},
+ {'id': 3, 'name': 'source-3', 'target': 1},
+ {'id': 4, 'name': 'source-4', 'target': 2},
]
self.assertEquals(serializer.data, expected)
def test_reverse_foreign_key_create(self):
- data = {'id': 3, 'name': u'target-3', 'sources': [1, 3]}
+ data = {'id': 3, 'name': 'target-3', 'sources': [1, 3]}
serializer = ForeignKeyTargetSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'target-3')
+ self.assertEqual(obj.name, 'target-3')
# Ensure target 3 is added, and everything else is as expected
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': [2]},
- {'id': 2, 'name': u'target-2', 'sources': []},
- {'id': 3, 'name': u'target-3', 'sources': [1, 3]},
+ {'id': 1, 'name': 'target-1', 'sources': [2]},
+ {'id': 2, 'name': 'target-2', 'sources': []},
+ {'id': 3, 'name': 'target-3', 'sources': [1, 3]},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update_with_invalid_null(self):
- data = {'id': 1, 'name': u'source-1', 'target': None}
+ data = {'id': 1, 'name': 'source-1', 'target': None}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'target': [u'Value may not be null']})
+ self.assertEquals(serializer.errors, {'target': ['Value may not be null']})
class PKNullableForeignKeyTests(TestCase):
@@ -287,28 +288,28 @@ class PKNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 1},
- {'id': 2, 'name': u'source-2', 'target': 1},
- {'id': 3, 'name': u'source-3', 'target': None},
+ {'id': 1, 'name': 'source-1', 'target': 1},
+ {'id': 2, 'name': 'source-2', 'target': 1},
+ {'id': 3, 'name': 'source-3', 'target': None},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_create_with_valid_null(self):
- data = {'id': 4, 'name': u'source-4', 'target': None}
+ data = {'id': 4, 'name': 'source-4', 'target': None}
serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 1},
- {'id': 2, 'name': u'source-2', 'target': 1},
- {'id': 3, 'name': u'source-3', 'target': None},
- {'id': 4, 'name': u'source-4', 'target': None}
+ {'id': 1, 'name': 'source-1', 'target': 1},
+ {'id': 2, 'name': 'source-2', 'target': 1},
+ {'id': 3, 'name': 'source-3', 'target': None},
+ {'id': 4, 'name': 'source-4', 'target': None}
]
self.assertEquals(serializer.data, expected)
@@ -317,27 +318,27 @@ class PKNullableForeignKeyTests(TestCase):
The emptystring should be interpreted as null in the context
of relationships.
"""
- data = {'id': 4, 'name': u'source-4', 'target': ''}
- expected_data = {'id': 4, 'name': u'source-4', 'target': None}
+ data = {'id': 4, 'name': 'source-4', 'target': ''}
+ expected_data = {'id': 4, 'name': 'source-4', 'target': None}
serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, expected_data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 1},
- {'id': 2, 'name': u'source-2', 'target': 1},
- {'id': 3, 'name': u'source-3', 'target': None},
- {'id': 4, 'name': u'source-4', 'target': None}
+ {'id': 1, 'name': 'source-1', 'target': 1},
+ {'id': 2, 'name': 'source-2', 'target': 1},
+ {'id': 3, 'name': 'source-3', 'target': None},
+ {'id': 4, 'name': 'source-4', 'target': None}
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update_with_valid_null(self):
- data = {'id': 1, 'name': u'source-1', 'target': None}
+ data = {'id': 1, 'name': 'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -348,9 +349,9 @@ class PKNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': None},
- {'id': 2, 'name': u'source-2', 'target': 1},
- {'id': 3, 'name': u'source-3', 'target': None}
+ {'id': 1, 'name': 'source-1', 'target': None},
+ {'id': 2, 'name': 'source-2', 'target': 1},
+ {'id': 3, 'name': 'source-3', 'target': None}
]
self.assertEquals(serializer.data, expected)
@@ -359,8 +360,8 @@ class PKNullableForeignKeyTests(TestCase):
The emptystring should be interpreted as null in the context
of relationships.
"""
- data = {'id': 1, 'name': u'source-1', 'target': ''}
- expected_data = {'id': 1, 'name': u'source-1', 'target': None}
+ data = {'id': 1, 'name': 'source-1', 'target': ''}
+ expected_data = {'id': 1, 'name': 'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -371,9 +372,9 @@ class PKNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': None},
- {'id': 2, 'name': u'source-2', 'target': 1},
- {'id': 3, 'name': u'source-3', 'target': None}
+ {'id': 1, 'name': 'source-1', 'target': None},
+ {'id': 2, 'name': 'source-2', 'target': 1},
+ {'id': 3, 'name': 'source-3', 'target': None}
]
self.assertEquals(serializer.data, expected)
@@ -382,7 +383,7 @@ class PKNullableForeignKeyTests(TestCase):
# and cannot be arbitrarily set.
# def test_reverse_foreign_key_update(self):
- # data = {'id': 1, 'name': u'target-1', 'sources': [1]}
+ # data = {'id': 1, 'name': 'target-1', 'sources': [1]}
# instance = ForeignKeyTarget.objects.get(pk=1)
# serializer = ForeignKeyTargetSerializer(instance, data=data)
# self.assertTrue(serializer.is_valid())
@@ -393,8 +394,8 @@ class PKNullableForeignKeyTests(TestCase):
# queryset = ForeignKeyTarget.objects.all()
# serializer = ForeignKeyTargetSerializer(queryset)
# expected = [
- # {'id': 1, 'name': u'target-1', 'sources': [1]},
- # {'id': 2, 'name': u'target-2', 'sources': []},
+ # {'id': 1, 'name': 'target-1', 'sources': [1]},
+ # {'id': 2, 'name': 'target-2', 'sources': []},
# ]
# self.assertEquals(serializer.data, expected)
@@ -412,7 +413,7 @@ class PKNullableOneToOneTests(TestCase):
queryset = OneToOneTarget.objects.all()
serializer = NullableOneToOneTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'nullable_source': 1},
- {'id': 2, 'name': u'target-2', 'nullable_source': None},
+ {'id': 1, 'name': 'target-1', 'nullable_source': 1},
+ {'id': 2, 'name': 'target-2', 'nullable_source': None},
]
self.assertEquals(serializer.data, expected)
diff --git a/rest_framework/tests/relations_slug.py b/rest_framework/tests/relations_slug.py
index 37ccc75e..c5558ec5 100644
--- a/rest_framework/tests/relations_slug.py
+++ b/rest_framework/tests/relations_slug.py
@@ -39,9 +39,9 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 'target-1'},
- {'id': 2, 'name': u'source-2', 'target': 'target-1'},
- {'id': 3, 'name': u'source-3', 'target': 'target-1'}
+ {'id': 1, 'name': 'source-1', 'target': 'target-1'},
+ {'id': 2, 'name': 'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': 'source-3', 'target': 'target-1'}
]
self.assertEquals(serializer.data, expected)
@@ -49,13 +49,13 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': ['source-1', 'source-2', 'source-3']},
- {'id': 2, 'name': u'target-2', 'sources': []},
+ {'id': 1, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']},
+ {'id': 2, 'name': 'target-2', 'sources': []},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update(self):
- data = {'id': 1, 'name': u'source-1', 'target': 'target-2'}
+ data = {'id': 1, 'name': 'source-1', 'target': 'target-2'}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -66,21 +66,21 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 'target-2'},
- {'id': 2, 'name': u'source-2', 'target': 'target-1'},
- {'id': 3, 'name': u'source-3', 'target': 'target-1'}
+ {'id': 1, 'name': 'source-1', 'target': 'target-2'},
+ {'id': 2, 'name': 'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': 'source-3', 'target': 'target-1'}
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update_incorrect_type(self):
- data = {'id': 1, 'name': u'source-1', 'target': 123}
+ data = {'id': 1, 'name': 'source-1', 'target': 123}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'target': [u'Object with name=123 does not exist.']})
+ self.assertEquals(serializer.errors, {'target': ['Object with name=123 does not exist.']})
def test_reverse_foreign_key_update(self):
- data = {'id': 2, 'name': u'target-2', 'sources': ['source-1', 'source-3']}
+ data = {'id': 2, 'name': 'target-2', 'sources': ['source-1', 'source-3']}
instance = ForeignKeyTarget.objects.get(pk=2)
serializer = ForeignKeyTargetSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -89,8 +89,8 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
new_serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': ['source-1', 'source-2', 'source-3']},
- {'id': 2, 'name': u'target-2', 'sources': []},
+ {'id': 1, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']},
+ {'id': 2, 'name': 'target-2', 'sources': []},
]
self.assertEquals(new_serializer.data, expected)
@@ -101,55 +101,55 @@ class PKForeignKeyTests(TestCase):
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': ['source-2']},
- {'id': 2, 'name': u'target-2', 'sources': ['source-1', 'source-3']},
+ {'id': 1, 'name': 'target-1', 'sources': ['source-2']},
+ {'id': 2, 'name': 'target-2', 'sources': ['source-1', 'source-3']},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_create(self):
- data = {'id': 4, 'name': u'source-4', 'target': 'target-2'}
+ data = {'id': 4, 'name': 'source-4', 'target': 'target-2'}
serializer = ForeignKeySourceSerializer(data=data)
serializer.is_valid()
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is added, and everything else is as expected
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 'target-1'},
- {'id': 2, 'name': u'source-2', 'target': 'target-1'},
- {'id': 3, 'name': u'source-3', 'target': 'target-1'},
- {'id': 4, 'name': u'source-4', 'target': 'target-2'},
+ {'id': 1, 'name': 'source-1', 'target': 'target-1'},
+ {'id': 2, 'name': 'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': 'source-3', 'target': 'target-1'},
+ {'id': 4, 'name': 'source-4', 'target': 'target-2'},
]
self.assertEquals(serializer.data, expected)
def test_reverse_foreign_key_create(self):
- data = {'id': 3, 'name': u'target-3', 'sources': ['source-1', 'source-3']}
+ data = {'id': 3, 'name': 'target-3', 'sources': ['source-1', 'source-3']}
serializer = ForeignKeyTargetSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'target-3')
+ self.assertEqual(obj.name, 'target-3')
# Ensure target 3 is added, and everything else is as expected
queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(queryset)
expected = [
- {'id': 1, 'name': u'target-1', 'sources': ['source-2']},
- {'id': 2, 'name': u'target-2', 'sources': []},
- {'id': 3, 'name': u'target-3', 'sources': ['source-1', 'source-3']},
+ {'id': 1, 'name': 'target-1', 'sources': ['source-2']},
+ {'id': 2, 'name': 'target-2', 'sources': []},
+ {'id': 3, 'name': 'target-3', 'sources': ['source-1', 'source-3']},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update_with_invalid_null(self):
- data = {'id': 1, 'name': u'source-1', 'target': None}
+ data = {'id': 1, 'name': 'source-1', 'target': None}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'target': [u'Value may not be null']})
+ self.assertEquals(serializer.errors, {'target': ['This field is required.']})
class SlugNullableForeignKeyTests(TestCase):
@@ -166,28 +166,28 @@ class SlugNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 'target-1'},
- {'id': 2, 'name': u'source-2', 'target': 'target-1'},
- {'id': 3, 'name': u'source-3', 'target': None},
+ {'id': 1, 'name': 'source-1', 'target': 'target-1'},
+ {'id': 2, 'name': 'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': 'source-3', 'target': None},
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_create_with_valid_null(self):
- data = {'id': 4, 'name': u'source-4', 'target': None}
+ data = {'id': 4, 'name': 'source-4', 'target': None}
serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 'target-1'},
- {'id': 2, 'name': u'source-2', 'target': 'target-1'},
- {'id': 3, 'name': u'source-3', 'target': None},
- {'id': 4, 'name': u'source-4', 'target': None}
+ {'id': 1, 'name': 'source-1', 'target': 'target-1'},
+ {'id': 2, 'name': 'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': 'source-3', 'target': None},
+ {'id': 4, 'name': 'source-4', 'target': None}
]
self.assertEquals(serializer.data, expected)
@@ -196,27 +196,27 @@ class SlugNullableForeignKeyTests(TestCase):
The emptystring should be interpreted as null in the context
of relationships.
"""
- data = {'id': 4, 'name': u'source-4', 'target': ''}
- expected_data = {'id': 4, 'name': u'source-4', 'target': None}
+ data = {'id': 4, 'name': 'source-4', 'target': ''}
+ expected_data = {'id': 4, 'name': 'source-4', 'target': None}
serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, expected_data)
- self.assertEqual(obj.name, u'source-4')
+ self.assertEqual(obj.name, 'source-4')
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': 'target-1'},
- {'id': 2, 'name': u'source-2', 'target': 'target-1'},
- {'id': 3, 'name': u'source-3', 'target': None},
- {'id': 4, 'name': u'source-4', 'target': None}
+ {'id': 1, 'name': 'source-1', 'target': 'target-1'},
+ {'id': 2, 'name': 'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': 'source-3', 'target': None},
+ {'id': 4, 'name': 'source-4', 'target': None}
]
self.assertEquals(serializer.data, expected)
def test_foreign_key_update_with_valid_null(self):
- data = {'id': 1, 'name': u'source-1', 'target': None}
+ data = {'id': 1, 'name': 'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -227,9 +227,9 @@ class SlugNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': None},
- {'id': 2, 'name': u'source-2', 'target': 'target-1'},
- {'id': 3, 'name': u'source-3', 'target': None}
+ {'id': 1, 'name': 'source-1', 'target': None},
+ {'id': 2, 'name': 'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': 'source-3', 'target': None}
]
self.assertEquals(serializer.data, expected)
@@ -238,8 +238,8 @@ class SlugNullableForeignKeyTests(TestCase):
The emptystring should be interpreted as null in the context
of relationships.
"""
- data = {'id': 1, 'name': u'source-1', 'target': ''}
- expected_data = {'id': 1, 'name': u'source-1', 'target': None}
+ data = {'id': 1, 'name': 'source-1', 'target': ''}
+ expected_data = {'id': 1, 'name': 'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
@@ -250,8 +250,8 @@ class SlugNullableForeignKeyTests(TestCase):
queryset = NullableForeignKeySource.objects.all()
serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
- {'id': 1, 'name': u'source-1', 'target': None},
- {'id': 2, 'name': u'source-2', 'target': 'target-1'},
- {'id': 3, 'name': u'source-3', 'target': None}
+ {'id': 1, 'name': 'source-1', 'target': None},
+ {'id': 2, 'name': 'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': 'source-3', 'target': None}
]
self.assertEquals(serializer.data, expected)
diff --git a/rest_framework/tests/renderers.py b/rest_framework/tests/renderers.py
index c1b4e624..72405336 100644
--- a/rest_framework/tests/renderers.py
+++ b/rest_framework/tests/renderers.py
@@ -14,7 +14,8 @@ 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
+from rest_framework.compat import six
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 4b032405..9d4fdc7b 100644
--- a/rest_framework/tests/request.py
+++ b/rest_framework/tests/request.py
@@ -1,7 +1,7 @@
"""
Tests for content parsing, and form-overloaded content parsing.
"""
-import json
+from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
from django.contrib.sessions.middleware import SessionMiddleware
@@ -20,6 +20,8 @@ from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
+from rest_framework.compat import six
+import json
factory = RequestFactory()
@@ -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..3e1da905 100644
--- a/rest_framework/tests/response.py
+++ b/rest_framework/tests/response.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.test import TestCase
from rest_framework.compat import patterns, url, include
from rest_framework.response import Response
@@ -9,6 +10,7 @@ from rest_framework.renderers import (
BrowsableAPIRenderer
)
from rest_framework.settings import api_settings
+from rest_framework.compat import six
class MockPickleRenderer(BaseRenderer):
@@ -22,8 +24,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 +94,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/reverse.py b/rest_framework/tests/reverse.py
index 8c86e1fb..4ad4d684 100644
--- a/rest_framework/tests/reverse.py
+++ b/rest_framework/tests/reverse.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.test import TestCase
from django.test.client import RequestFactory
from rest_framework.compat import patterns, url
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 88ad6e85..a0e1474f 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -1,10 +1,12 @@
-import datetime
-import pickle
+from __future__ import unicode_literals
+from django.utils.datastructures import MultiValueDict
from django.test import TestCase
from rest_framework import serializers
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,
ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo)
+import datetime
+import pickle
class SubComment(object):
@@ -200,12 +202,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 = {
@@ -214,7 +216,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
@@ -234,17 +236,17 @@ class ValidationTests(TestCase):
data = ['i am', 'a', 'list']
serializer = CommentSerializer(self.comment, data=data)
self.assertEquals(serializer.is_valid(), False)
- self.assertEquals(serializer.errors, {'non_field_errors': [u'Invalid data']})
+ self.assertEquals(serializer.errors, {'non_field_errors': ['Invalid data']})
data = 'and i am a string'
serializer = CommentSerializer(self.comment, data=data)
self.assertEquals(serializer.is_valid(), False)
- self.assertEquals(serializer.errors, {'non_field_errors': [u'Invalid data']})
+ self.assertEquals(serializer.errors, {'non_field_errors': ['Invalid data']})
data = 42
serializer = CommentSerializer(self.comment, data=data)
self.assertEquals(serializer.is_valid(), False)
- self.assertEquals(serializer.errors, {'non_field_errors': [u'Invalid data']})
+ self.assertEquals(serializer.errors, {'non_field_errors': ['Invalid data']})
def test_cross_field_validation(self):
@@ -268,7 +270,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):
"""
@@ -284,7 +286,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_modelserializer_max_length_exceeded_with_custom_restore(self):
"""
@@ -298,7 +300,7 @@ class ValidationTests(TestCase):
}
serializer = ActionItemSerializerCustomRestore(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 = {
@@ -307,7 +309,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 CustomValidationTests(TestCase):
@@ -338,7 +340,7 @@ class CustomValidationTests(TestCase):
serializer = self.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_missing_data(self):
"""
@@ -350,7 +352,7 @@ class CustomValidationTests(TestCase):
}
serializer = self.CommentSerializerWithFieldValidator(data=incomplete_data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'content': [u'This field is required.']})
+ self.assertEquals(serializer.errors, {'content': ['This field is required.']})
def test_wrong_data(self):
"""
@@ -363,7 +365,7 @@ class CustomValidationTests(TestCase):
}
serializer = self.CommentSerializerWithFieldValidator(data=wrong_data)
self.assertFalse(serializer.is_valid())
- self.assertEquals(serializer.errors, {'email': [u'Enter a valid e-mail address.']})
+ self.assertEquals(serializer.errors, {'email': ['Enter a valid e-mail address.']})
class PositiveIntegerAsChoiceTests(TestCase):
@@ -383,7 +385,7 @@ class ModelValidationTests(TestCase):
serializer.save()
second_serializer = AlbumsSerializer(data={'title': 'a'})
self.assertFalse(second_serializer.is_valid())
- self.assertEqual(second_serializer.errors, {'title': [u'Album with this Title already exists.']})
+ self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.']})
def test_foreign_key_with_partial(self):
"""
@@ -421,15 +423,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'})
@@ -536,7 +538,8 @@ class ManyToManyTests(TestCase):
containing no items, using a representation that does not support
lists (eg form data).
"""
- data = {'rel': ''}
+ data = MultiValueDict()
+ data.setlist('rel', [''])
serializer = self.serializer_class(data=data)
self.assertEquals(serializer.is_valid(), True)
instance = serializer.save()
@@ -743,11 +746,11 @@ class RelatedTraversalTest(TestCase):
serializer = BlogPostSerializer(instance=post)
expected = {
- 'title': u'Test blog post',
+ 'title': 'Test blog post',
'comments': [{
- 'text': u'I love this blog post',
+ 'text': 'I love this blog post',
'post_owner': {
- "name": u"django",
+ "name": "django",
"age": None
}
}]
@@ -782,8 +785,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,
}
@@ -882,8 +885,8 @@ class DepthTest(TestCase):
depth = 1
serializer = BlogPostSerializer(instance=post)
- expected = {'id': 1, 'title': u'Test blog post',
- 'writer': {'id': 1, 'name': u'django', 'age': 1}}
+ expected = {'id': 1, 'title': 'Test blog post',
+ 'writer': {'id': 1, 'name': 'django', 'age': 1}}
self.assertEqual(serializer.data, expected)
@@ -902,8 +905,8 @@ class DepthTest(TestCase):
model = BlogPost
serializer = BlogPostSerializer(instance=post)
- expected = {'id': 1, 'title': u'Test blog post',
- 'writer': {'id': 1, 'name': u'django', 'age': 1}}
+ expected = {'id': 1, 'title': 'Test blog post',
+ 'writer': {'id': 1, 'name': 'django', 'age': 1}}
self.assertEqual(serializer.data, expected)
diff --git a/rest_framework/tests/settings.py b/rest_framework/tests/settings.py
index 0293fdc3..857375c2 100644
--- a/rest_framework/tests/settings.py
+++ b/rest_framework/tests/settings.py
@@ -1,4 +1,5 @@
"""Tests for the settings module"""
+from __future__ import unicode_literals
from django.test import TestCase
from rest_framework.settings import APISettings, DEFAULTS, IMPORT_STRINGS
diff --git a/rest_framework/tests/status.py b/rest_framework/tests/status.py
index 30df5cef..c0d11b5f 100644
--- a/rest_framework/tests/status.py
+++ b/rest_framework/tests/status.py
@@ -1,4 +1,5 @@
"""Tests for the status module"""
+from __future__ import unicode_literals
from django.test import TestCase
from rest_framework import status
diff --git a/rest_framework/tests/testcases.py b/rest_framework/tests/testcases.py
index 97f492ff..f8c2579e 100644
--- a/rest_framework/tests/testcases.py
+++ b/rest_framework/tests/testcases.py
@@ -1,4 +1,5 @@
# http://djangosnippets.org/snippets/1011/
+from __future__ import unicode_literals
from django.conf import settings
from django.core.management import call_command
from django.db.models import loading
diff --git a/rest_framework/tests/tests.py b/rest_framework/tests/tests.py
index adeaf6da..08f88e11 100644
--- a/rest_framework/tests/tests.py
+++ b/rest_framework/tests/tests.py
@@ -2,6 +2,7 @@
Force import of all modules in this package in order to get the standard test
runner to pick up the tests. Yowzers.
"""
+from __future__ import unicode_literals
import os
modules = [filename.rsplit('.', 1)[0]
diff --git a/rest_framework/tests/throttling.py b/rest_framework/tests/throttling.py
index 4b98b941..4616f325 100644
--- a/rest_framework/tests/throttling.py
+++ b/rest_framework/tests/throttling.py
@@ -1,11 +1,10 @@
"""
Tests for the throttling implementations in the permissions module.
"""
-
+from __future__ import unicode_literals
from django.test import TestCase
from django.contrib.auth.models import User
from django.core.cache import cache
-
from django.test.client import RequestFactory
from rest_framework.views import APIView
from rest_framework.throttling import UserRateThrottle
diff --git a/rest_framework/tests/urlpatterns.py b/rest_framework/tests/urlpatterns.py
index 43e8ef69..41245ad1 100644
--- a/rest_framework/tests/urlpatterns.py
+++ b/rest_framework/tests/urlpatterns.py
@@ -1,10 +1,8 @@
+from __future__ import unicode_literals
from collections import namedtuple
-
from django.core import urlresolvers
-
from django.test import TestCase
from django.test.client import RequestFactory
-
from rest_framework.compat import patterns, url, include
from rest_framework.urlpatterns import format_suffix_patterns
diff --git a/rest_framework/tests/utils.py b/rest_framework/tests/utils.py
index 3906adb9..224c4f9d 100644
--- a/rest_framework/tests/utils.py
+++ b/rest_framework/tests/utils.py
@@ -1,6 +1,7 @@
+from __future__ import unicode_literals
from django.test.client import RequestFactory, FakePayload
from django.test.client import MULTIPART_CONTENT
-from urlparse import urlparse
+from rest_framework.compat import urlparse
class RequestFactory(RequestFactory):
@@ -14,7 +15,7 @@ class RequestFactory(RequestFactory):
patch_data = self._encode_data(data, content_type)
- parsed = urlparse(path)
+ parsed = urlparse.urlparse(path)
r = {
'CONTENT_LENGTH': len(patch_data),
'CONTENT_TYPE': content_type,
diff --git a/rest_framework/tests/validators.py b/rest_framework/tests/validators.py
index c032985e..8844cb74 100644
--- a/rest_framework/tests/validators.py
+++ b/rest_framework/tests/validators.py
@@ -139,7 +139,7 @@
# raise errors on unexpected request data"""
# content = {'qwerty': 'uiop', 'extra': 'extra'}
# validator.allow_unknown_form_fields = True
-# self.assertEqual({'qwerty': u'uiop'},
+# self.assertEqual({'qwerty': 'uiop'},
# validator.validate_request(content, None),
# "Resource didn't accept unknown fields.")
# validator.allow_unknown_form_fields = False
diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py
index 7cd82656..7063c3fb 100644
--- a/rest_framework/tests/views.py
+++ b/rest_framework/tests/views.py
@@ -1,4 +1,4 @@
-import copy
+from __future__ import unicode_literals
from django.test import TestCase
from django.test.client import RequestFactory
from rest_framework import status
@@ -6,6 +6,7 @@ from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
+import copy
factory = RequestFactory()
@@ -49,7 +50,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)
@@ -64,7 +65,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)
@@ -78,7 +79,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)
@@ -93,7 +94,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/throttling.py b/rest_framework/throttling.py
index 8fe64248..810cad63 100644
--- a/rest_framework/throttling.py
+++ b/rest_framework/throttling.py
@@ -1,7 +1,8 @@
-import time
+from __future__ import unicode_literals
from django.core.cache import cache
from rest_framework import exceptions
from rest_framework.settings import api_settings
+import time
class BaseThrottle(object):
diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py
index 47789026..d9143bb4 100644
--- a/rest_framework/urlpatterns.py
+++ b/rest_framework/urlpatterns.py
@@ -1,6 +1,7 @@
+from __future__ import unicode_literals
+from django.core.urlresolvers import RegexURLResolver
from rest_framework.compat import url, include
from rest_framework.settings import api_settings
-from django.core.urlresolvers import RegexURLResolver
def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required):
diff --git a/rest_framework/urls.py b/rest_framework/urls.py
index fbe4bc07..9c4719f1 100644
--- a/rest_framework/urls.py
+++ b/rest_framework/urls.py
@@ -12,6 +12,7 @@ your authentication settings include `SessionAuthentication`.
url(r'^auth', include('rest_framework.urls', namespace='rest_framework'))
)
"""
+from __future__ import unicode_literals
from rest_framework.compat import patterns, url
diff --git a/rest_framework/utils/__init__.py b/rest_framework/utils/__init__.py
index 84fcb5db..3bab3b5f 100644
--- a/rest_framework/utils/__init__.py
+++ b/rest_framework/utils/__init__.py
@@ -1,6 +1,8 @@
-from django.utils.encoding import smart_unicode
+from __future__ import unicode_literals
from django.utils.xmlutils import SimplerXMLGenerator
from rest_framework.compat import StringIO
+from rest_framework.compat import six
+from rest_framework.compat import smart_text
import re
import xml.etree.ElementTree as ET
@@ -70,7 +72,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 +82,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/breadcrumbs.py b/rest_framework/utils/breadcrumbs.py
index 80e39d46..af21ac79 100644
--- a/rest_framework/utils/breadcrumbs.py
+++ b/rest_framework/utils/breadcrumbs.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from django.core.urlresolvers import resolve, get_script_prefix
diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py
index 7afe100a..b6de18a8 100644
--- a/rest_framework/utils/encoders.py
+++ b/rest_framework/utils/encoders.py
@@ -1,13 +1,14 @@
"""
Helper classes for parsers.
"""
+from __future__ import unicode_literals
+from django.utils.datastructures import SortedDict
+from rest_framework.compat import timezone
+from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
import datetime
import decimal
import types
import json
-from django.utils.datastructures import SortedDict
-from rest_framework.compat import timezone
-from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
class JSONEncoder(json.JSONEncoder):
diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py
index ee7f3a54..c09c2933 100644
--- a/rest_framework/utils/mediatypes.py
+++ b/rest_framework/utils/mediatypes.py
@@ -3,8 +3,9 @@ Handling of media types, as found in HTTP Content-Type and Accept headers.
See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
"""
-
+from __future__ import unicode_literals
from django.http.multipartparser import parse_header
+from rest_framework import HTTP_HEADER_ENCODING
def media_type_matches(lhs, rhs):
@@ -47,7 +48,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(HTTP_HEADER_ENCODING))
self.main_type, sep, self.sub_type = self.full_type.partition('/')
def match(self, other):
diff --git a/rest_framework/views.py b/rest_framework/views.py
index ac9b3385..ef2b5f92 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -1,8 +1,7 @@
"""
Provides an APIView class that is used as the base of all class-based views.
"""
-
-import re
+from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.utils.html import escape
@@ -13,6 +12,7 @@ from rest_framework.compat import View, apply_markdown
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.settings import api_settings
+import re
def _remove_trailing_string(content, trailing):