aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorXavier Ordoquy2014-06-23 14:02:45 +0200
committerXavier Ordoquy2014-06-23 14:02:45 +0200
commit2489e38a06f575aa144644eee683bd87f20186ef (patch)
treefc52fd2280d3ef6d0dc69d53a3771ee5f499ab29 /rest_framework
parent15c2c58b43a00ec29af99e0478b70eea57560fce (diff)
parente11f41ebc4ef088a5849771dfda5a7fba4f82904 (diff)
downloaddjango-rest-framework-2489e38a06f575aa144644eee683bd87f20186ef.tar.bz2
Merge remote-tracking branch 'origin/master' into 2.4.0
Conflicts: .travis.yml docs/api-guide/viewsets.md rest_framework/serializers.py rest_framework/throttling.py tests/test_generics.py tests/test_serializers.py tox.ini
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/authtoken/serializers.py11
-rw-r--r--rest_framework/compat.py1
-rw-r--r--rest_framework/fields.py23
-rw-r--r--rest_framework/generics.py8
-rw-r--r--rest_framework/serializers.py30
-rw-r--r--rest_framework/templatetags/rest_framework.py4
-rw-r--r--rest_framework/test.py2
-rw-r--r--rest_framework/throttling.py6
9 files changed, 60 insertions, 27 deletions
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 2d76b55d..01036cef 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __ _
"""
__title__ = 'Django REST framework'
-__version__ = '2.3.13'
+__version__ = '2.3.14'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2014 Tom Christie'
diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py
index 60a3740e..99e99ae3 100644
--- a/rest_framework/authtoken/serializers.py
+++ b/rest_framework/authtoken/serializers.py
@@ -1,4 +1,6 @@
from django.contrib.auth import authenticate
+from django.utils.translation import ugettext_lazy as _
+
from rest_framework import serializers
@@ -15,10 +17,13 @@ class AuthTokenSerializer(serializers.Serializer):
if user:
if not user.is_active:
- raise serializers.ValidationError('User account is disabled.')
+ msg = _('User account is disabled.')
+ raise serializers.ValidationError(msg)
attrs['user'] = user
return attrs
else:
- raise serializers.ValidationError('Unable to login with provided credentials.')
+ msg = _('Unable to login with provided credentials.')
+ raise serializers.ValidationError(msg)
else:
- raise serializers.ValidationError('Must include "username" and "password"')
+ msg = _('Must include "username" and "password"')
+ raise serializers.ValidationError(msg)
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index a013a155..6a5cbbe4 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -47,6 +47,7 @@ except ImportError:
# django-guardian is optional
try:
import guardian
+ import guardian.shortcuts # Fixes #1624
except ImportError:
guardian = None
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 8eaf763d..e925c47e 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -64,7 +64,7 @@ def get_component(obj, attr_name):
def readable_datetime_formats(formats):
format = ', '.join(formats).replace(ISO_8601,
- 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]')
+ 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]')
return humanize_strptime(format)
@@ -156,7 +156,12 @@ class Field(object):
def widget_html(self):
if not self.widget:
return ''
- return self.widget.render(self._name, self._value)
+
+ attrs = {}
+ if 'id' not in self.widget.attrs:
+ attrs['id'] = self._name
+
+ return self.widget.render(self._name, self._value, attrs=attrs)
def label_tag(self):
return '<label for="%s">%s:</label>' % (self._name, self.label)
@@ -499,7 +504,7 @@ class SlugField(CharField):
class ChoiceField(WritableField):
type_name = 'ChoiceField'
- type_label = 'multiple choice'
+ type_label = 'choice'
form_field_class = forms.ChoiceField
widget = widgets.Select
default_error_messages = {
@@ -507,12 +512,16 @@ class ChoiceField(WritableField):
'the available choices.'),
}
- def __init__(self, choices=(), *args, **kwargs):
+ def __init__(self, choices=(), blank_display_value=None, *args, **kwargs):
self.empty = kwargs.pop('empty', '')
super(ChoiceField, self).__init__(*args, **kwargs)
self.choices = choices
if not self.required:
- self.choices = BLANK_CHOICE_DASH + self.choices
+ if blank_display_value is None:
+ blank_choice = BLANK_CHOICE_DASH
+ else:
+ blank_choice = [('', blank_display_value)]
+ self.choices = blank_choice + self.choices
def _get_choices(self):
return self._choices
@@ -1016,9 +1025,9 @@ class SerializerMethodField(Field):
A field that gets its value by calling a method on the serializer it's attached to.
"""
- def __init__(self, method_name):
+ def __init__(self, method_name, *args, **kwargs):
self.method_name = method_name
- super(SerializerMethodField, self).__init__()
+ super(SerializerMethodField, self).__init__(*args, **kwargs)
def field_to_native(self, obj, field_name):
value = getattr(self.parent, self.method_name)(obj)
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index c3256844..e38c52b1 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -90,8 +90,8 @@ class GenericAPIView(views.APIView):
'view': self
}
- def get_serializer(self, instance=None, data=None,
- files=None, many=False, partial=False):
+ def get_serializer(self, instance=None, data=None, files=None, many=False,
+ partial=False, allow_add_remove=False):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
@@ -99,7 +99,9 @@ class GenericAPIView(views.APIView):
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(instance, data=data, files=files,
- many=many, partial=partial, context=context)
+ many=many, partial=partial,
+ allow_add_remove=allow_add_remove,
+ context=context)
def get_pagination_serializer(self, page):
"""
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 9709d56d..a15b8f22 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -21,6 +21,7 @@ from django.core.paginator import Page
from django.db import models
from django.forms import widgets
from django.utils.datastructures import SortedDict
+from django.core.exceptions import ObjectDoesNotExist
from rest_framework.compat import six
from rest_framework.settings import api_settings
@@ -32,8 +33,8 @@ from rest_framework.settings import api_settings
# This helps keep the separation between model fields, form fields, and
# serializer fields more explicit.
-from rest_framework.relations import *
-from rest_framework.fields import *
+from rest_framework.relations import * # NOQA
+from rest_framework.fields import * # NOQA
def _resolve_model(obj):
@@ -48,7 +49,7 @@ def _resolve_model(obj):
String representations should have the format:
'appname.ModelName'
"""
- if type(obj) == str and len(obj.split('.')) == 2:
+ if isinstance(obj, six.string_types) and len(obj.split('.')) == 2:
app_name, model_name = obj.split('.')
return models.get_model(app_name, model_name)
elif inspect.isclass(obj) and issubclass(obj, models.Model):
@@ -344,7 +345,7 @@ class BaseSerializer(WritableField):
for field_name, field in self.fields.items():
if field.read_only and obj is None:
- continue
+ continue
field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name)
@@ -753,9 +754,9 @@ class ModelSerializer(Serializer):
field.read_only = True
ret[accessor_name] = field
-
+
# Ensure that 'read_only_fields' is an iterable
- assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple'
+ assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple'
# Add the `read_only` flag to any fields that have been specified
# in the `read_only_fields` option
@@ -770,10 +771,10 @@ class ModelSerializer(Serializer):
"on serializer '%s'." %
(field_name, self.__class__.__name__))
ret[field_name].read_only = True
-
+
# Ensure that 'write_only_fields' is an iterable
- assert isinstance(self.opts.write_only_fields, (list, tuple)), '`write_only_fields` must be a list or tuple'
-
+ assert isinstance(self.opts.write_only_fields, (list, tuple)), '`write_only_fields` must be a list or tuple'
+
for field_name in self.opts.write_only_fields:
assert field_name not in self.base_fields.keys(), (
"field '%s' on serializer '%s' specified in "
@@ -784,7 +785,7 @@ class ModelSerializer(Serializer):
"Non-existant field '%s' specified in `write_only_fields` "
"on serializer '%s'." %
(field_name, self.__class__.__name__))
- ret[field_name].write_only = True
+ ret[field_name].write_only = True
return ret
@@ -828,6 +829,15 @@ class ModelSerializer(Serializer):
if model_field.verbose_name is not None:
kwargs['label'] = model_field.verbose_name
+ if not model_field.editable:
+ kwargs['read_only'] = True
+
+ if model_field.verbose_name is not None:
+ kwargs['label'] = model_field.verbose_name
+
+ if model_field.help_text is not None:
+ kwargs['help_text'] = model_field.help_text
+
return PrimaryKeyRelatedField(**kwargs)
def get_field(self, model_field):
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index 886db63c..911b1b62 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -40,7 +40,7 @@ def optional_login(request):
except NoReverseMatch:
return ''
- snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, request.path)
+ snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, escape(request.path))
return snippet
@@ -54,7 +54,7 @@ def optional_logout(request):
except NoReverseMatch:
return ''
- snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, request.path)
+ snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, escape(request.path))
return snippet
diff --git a/rest_framework/test.py b/rest_framework/test.py
index df5a5b3b..284bcee0 100644
--- a/rest_framework/test.py
+++ b/rest_framework/test.py
@@ -36,7 +36,7 @@ class APIRequestFactory(DjangoRequestFactory):
"""
if not data:
- return ('', None)
+ return ('', content_type)
assert format is None or content_type is None, (
'You may not set both `format` and `content_type`.'
diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py
index fc24c92e..7e9f9d71 100644
--- a/rest_framework/throttling.py
+++ b/rest_framework/throttling.py
@@ -173,6 +173,12 @@ class AnonRateThrottle(SimpleRateThrottle):
if request.user.is_authenticated():
return None # Only throttle unauthenticated requests.
+ ident = request.META.get('HTTP_X_FORWARDED_FOR')
+ if ident is None:
+ ident = request.META.get('REMOTE_ADDR')
+ else:
+ ident = ''.join(ident.split())
+
return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request)