aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorTom Christie2014-09-11 13:20:44 +0100
committerTom Christie2014-09-11 13:20:44 +0100
commitde301f3b6647e1c79a506405a88071ef977418d1 (patch)
tree407f3497b422f334b47088b0bb35d39a8a3a520a /rest_framework
parent80ba0473473501968154c5cc5dd5922e53d96a70 (diff)
parent015a8122c7738dd8913939b42d3f0ec932d88711 (diff)
downloaddjango-rest-framework-de301f3b6647e1c79a506405a88071ef977418d1.tar.bz2
Merge master
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/authentication.py2
-rw-r--r--rest_framework/fields.py7
-rw-r--r--rest_framework/filters.py1
-rw-r--r--rest_framework/pagination.py8
-rw-r--r--rest_framework/parsers.py20
-rw-r--r--rest_framework/routers.py4
-rw-r--r--rest_framework/serializers.py28
-rw-r--r--rest_framework/templates/rest_framework/base.html30
-rw-r--r--rest_framework/templates/rest_framework/login_base.html35
-rw-r--r--rest_framework/templatetags/rest_framework.py21
-rw-r--r--rest_framework/test.py2
-rw-r--r--rest_framework/urls.py2
-rw-r--r--rest_framework/utils/formatting.py6
-rw-r--r--rest_framework/views.py10
15 files changed, 124 insertions, 54 deletions
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index f95bdc22..8d82a4b9 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
-__version__ = '2.4.0'
+__version__ = '2.4.2'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2014 Tom Christie'
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index 5721a869..f3fec05e 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -344,7 +344,7 @@ class OAuth2Authentication(BaseAuthentication):
user = token.user
if not user.is_active:
- msg = 'User inactive or deleted: %s' % user.username
+ msg = 'User inactive or deleted: %s' % user.get_username()
raise exceptions.AuthenticationFailed(msg)
return (user, token)
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 3ec28908..7496a629 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -51,8 +51,11 @@ def get_attribute(instance, attrs):
for attr in attrs:
try:
instance = getattr(instance, attr)
- except AttributeError:
- return instance[attr]
+ except AttributeError as exc:
+ try:
+ return instance[attr]
+ except (KeyError, TypeError):
+ raise exc
return instance
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index e2080013..c580f935 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -56,7 +56,6 @@ class DjangoFilterBackend(BaseFilterBackend):
class Meta:
model = queryset.model
fields = filter_fields
- order_by = True
return AutoFilterSet
return None
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index 478d32b4..9cf31629 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -43,8 +43,9 @@ class DefaultObjectSerializer(serializers.Field):
as the default.
"""
- def __init__(self, source=None, context=None):
- # Note: Swallow context kwarg - only required for eg. ModelSerializer.
+ def __init__(self, source=None, many=None, context=None):
+ # Note: Swallow context and many kwargs - only required for
+ # eg. ModelSerializer.
super(DefaultObjectSerializer, self).__init__(source=source)
@@ -61,6 +62,7 @@ class BasePaginationSerializer(serializers.Serializer):
"""
super(BasePaginationSerializer, self).__init__(*args, **kwargs)
results_field = self.results_field
+
try:
object_serializer = self.Meta.object_serializer_class
except AttributeError:
@@ -70,7 +72,7 @@ class BasePaginationSerializer(serializers.Serializer):
child=object_serializer(),
source='object_list'
)
- self.fields[results_field].bind(results_field, self, self) # TODO: Support automatic binding
+ self.fields[results_field].bind(results_field, self, self)
class PaginationSerializer(BasePaginationSerializer):
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index aa4fd3f1..c287908d 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -11,7 +11,7 @@ from django.http import QueryDict
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter
from django.utils import six
-from rest_framework.compat import etree, yaml, force_text
+from rest_framework.compat import etree, yaml, force_text, urlparse
from rest_framework.exceptions import ParseError
from rest_framework import renderers
import json
@@ -290,6 +290,22 @@ class FileUploadParser(BaseParser):
try:
meta = parser_context['request'].META
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8'))
- return force_text(disposition[1]['filename'])
+ filename_parm = disposition[1]
+ if 'filename*' in filename_parm:
+ return self.get_encoded_filename(filename_parm)
+ return force_text(filename_parm['filename'])
except (AttributeError, KeyError):
pass
+
+ def get_encoded_filename(self, filename_parm):
+ """
+ Handle encoded filenames per RFC6266. See also:
+ http://tools.ietf.org/html/rfc2231#section-4
+ """
+ encoded_filename = force_text(filename_parm['filename*'])
+ try:
+ charset, lang, filename = encoded_filename.split('\'', 2)
+ filename = urlparse.unquote(filename)
+ except (ValueError, LookupError):
+ filename = force_text(filename_parm['filename'])
+ return filename
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index ae56673d..8f1ab6fa 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -284,10 +284,10 @@ class DefaultRouter(SimpleRouter):
class APIRoot(views.APIView):
_ignore_model_permissions = True
- def get(self, request, format=None):
+ def get(self, request, *args, **kwargs):
ret = {}
for key, url_name in api_root_dict.items():
- ret[key] = reverse(url_name, request=request, format=format)
+ ret[key] = reverse(url_name, request=request, format=kwargs.get('format', None))
return Response(ret)
return APIRoot.as_view()
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 13e57939..8fe999ae 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -317,6 +317,19 @@ class ModelSerializerOptions(object):
self.depth = getattr(meta, 'depth', 0)
+def lookup_class(mapping, obj):
+ """
+ Takes a dictionary with classes as keys, and an object.
+ Traverses the object's inheritance hierarchy in method
+ resolution order, and returns the first matching value
+ from the dictionary or None.
+ """
+ return next(
+ (mapping[cls] for cls in inspect.getmro(obj.__class__) if cls in mapping),
+ None
+ )
+
+
class ModelSerializer(Serializer):
field_mapping = {
models.AutoField: IntegerField,
@@ -580,13 +593,20 @@ class ModelSerializer(Serializer):
if decimal_places is not None:
kwargs['decimal_places'] = decimal_places
+ if isinstance(model_field, models.BooleanField):
+ # models.BooleanField has `blank=True`, but *is* actually
+ # required *unless* a default is provided.
+ # Also note that <1.6 `default=False`, >=1.6 `default=None`.
+ kwargs.pop('required', None)
+
if validator_kwarg:
kwargs['validators'] = validator_kwarg
- try:
- return self.field_mapping[model_field.__class__](**kwargs)
- except KeyError:
- return ModelField(model_field=model_field, **kwargs)
+ cls = lookup_class(self.field_mapping, model_field)
+ if cls is None:
+ cls = ModelField
+ kwargs['model_field'] = model_field
+ return cls(**kwargs)
class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html
index cee9724d..a84ccf26 100644
--- a/rest_framework/templates/rest_framework/base.html
+++ b/rest_framework/templates/rest_framework/base.html
@@ -5,14 +5,14 @@
<html>
<head>
{% block head %}
-
+
{% block meta %}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="robots" content="NONE,NOARCHIVE" />
{% endblock %}
-
+
<title>{% block title %}Django REST framework{% endblock %}</title>
-
+
{% block style %}
{% block bootstrap_theme %}
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/>
@@ -21,10 +21,11 @@
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/prettify.css" %}"/>
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/>
{% endblock %}
-
+
{% endblock %}
</head>
+ {% block body %}
<body class="{% block bodyclass %}{% endblock %} container">
<div class="wrapper">
@@ -43,17 +44,9 @@
<ul class="nav pull-right">
{% block userlinks %}
{% if user.is_authenticated %}
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown">
- {{ user }}
- <b class="caret"></b>
- </a>
- <ul class="dropdown-menu">
- <li>{% optional_logout request %}</li>
- </ul>
- </li>
+ {% optional_logout request user %}
{% else %}
- <li>{% optional_login request %}</li>
+ {% optional_login request %}
{% endif %}
{% endblock %}
</ul>
@@ -84,7 +77,7 @@
<div class="btn-group format-selection">
<a class="btn btn-primary js-tooltip" href='{{ request.get_full_path }}'
rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a>
-
+
<button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown"
title="Specify a format for the GET request">
<span class="caret"></span>
@@ -143,7 +136,7 @@
</div>
{% if display_edit_forms %}
-
+
{% if post_form or raw_data_post_form %}
<div {% if post_form %}class="tabbable"{% endif %}>
{% if post_form %}
@@ -189,7 +182,7 @@
</div>
</div>
{% endif %}
-
+
{% if put_form or raw_data_put_form or raw_data_patch_form %}
<div {% if put_form %}class="tabbable"{% endif %}>
{% if put_form %}
@@ -245,7 +238,7 @@
{% endif %}
</div>
<!-- END Content -->
-
+
<footer>
{% block footer %}
<p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</p>
@@ -261,4 +254,5 @@
<script src="{% static "rest_framework/js/default.js" %}"></script>
{% endblock %}
</body>
+ {% endblock %}
</html>
diff --git a/rest_framework/templates/rest_framework/login_base.html b/rest_framework/templates/rest_framework/login_base.html
index 43860e53..8ab682ac 100644
--- a/rest_framework/templates/rest_framework/login_base.html
+++ b/rest_framework/templates/rest_framework/login_base.html
@@ -17,21 +17,44 @@
<div class="row-fluid">
<div>
- <form action="{% url 'rest_framework:login' %}" class=" form-inline" method="post">
+ <form action="{% url 'rest_framework:login' %}" role="form" method="post">
{% csrf_token %}
- <div id="div_id_username" class="clearfix control-group">
+ <div id="div_id_username"
+ class="clearfix control-group {% if form.username.errors %}error{% endif %}">
<div class="controls">
<Label class="span4">Username:</label>
- <input style="height: 25px" type="text" name="username" maxlength="100" autocapitalize="off" autocorrect="off" class="textinput textInput" id="id_username">
+ <input style="height: 25px" type="text" name="username" maxlength="100"
+ autocapitalize="off"
+ autocorrect="off" class="span12 textinput textInput"
+ id="id_username" required
+ {% if form.username.value %}value="{{ form.username.value }}"{% endif %}>
+ {% if form.username.errors %}
+ <p class="text-error">
+ {{ form.username.errors|striptags }}
+ </p>
+ {% endif %}
</div>
</div>
- <div id="div_id_password" class="clearfix control-group">
- <div class="controls">
+ <div id="div_id_password"
+ class="clearfix control-group {% if form.password.errors %}error{% endif %}">
+ <div class="controls">
<Label class="span4">Password:</label>
- <input style="height: 25px" type="password" name="password" maxlength="100" autocapitalize="off" autocorrect="off" class="textinput textInput" id="id_password">
+ <input style="height: 25px" type="password" name="password" maxlength="100"
+ autocapitalize="off" autocorrect="off" class="span12 textinput textInput"
+ id="id_password" required>
+ {% if form.password.errors %}
+ <p class="text-error">
+ {{ form.password.errors|striptags }}
+ </p>
+ {% endif %}
</div>
</div>
<input type="hidden" name="next" value="{{ next }}" />
+ {% if form.non_field_errors %}
+ {% for error in form.non_field_errors %}
+ <div class="well well-small text-error" style="border: none">{{ error }}</div>
+ {% endfor %}
+ {% endif %}
<div class="form-actions-no-box">
<input type="submit" name="submit" value="Log in" class="btn btn-primary" id="submit-id-submit">
</div>
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index b80a7d77..864d64dd 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -41,22 +41,31 @@ def optional_login(request):
except NoReverseMatch:
return ''
- snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, escape(request.path))
+ snippet = "<li><a href='{href}?next={next}'>Log in</a></li>".format(href=login_url, next=escape(request.path))
return snippet
@register.simple_tag
-def optional_logout(request):
+def optional_logout(request, user):
"""
Include a logout snippet if REST framework's logout view is in the URLconf.
"""
try:
logout_url = reverse('rest_framework:logout')
except NoReverseMatch:
- return ''
-
- snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, escape(request.path))
- return snippet
+ return '<li class="navbar-text">{user}</li>'.format(user=user)
+
+ snippet = """<li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+ {user}
+ <b class="caret"></b>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a href='{href}?next={next}'>Log out</a></li>
+ </ul>
+ </li>"""
+
+ return snippet.format(user=user, href=logout_url, next=escape(request.path))
@register.simple_tag
diff --git a/rest_framework/test.py b/rest_framework/test.py
index f89a6dcd..9b40353a 100644
--- a/rest_framework/test.py
+++ b/rest_framework/test.py
@@ -36,7 +36,7 @@ class APIRequestFactory(DjangoRequestFactory):
Encode the data returning a two tuple of (bytes, content_type)
"""
- if not data:
+ if data is None:
return ('', content_type)
assert format is None or content_type is None, (
diff --git a/rest_framework/urls.py b/rest_framework/urls.py
index 8fa3073e..cfcee534 100644
--- a/rest_framework/urls.py
+++ b/rest_framework/urls.py
@@ -6,7 +6,7 @@ your API requires authentication:
urlpatterns = patterns('',
...
- url(r'^auth', include('rest_framework.urls', namespace='rest_framework'))
+ url(r'^auth/', include('rest_framework.urls', namespace='rest_framework'))
)
The urls must be namespaced as 'rest_framework', and you should make sure
diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py
index 6d53aed1..470af51b 100644
--- a/rest_framework/utils/formatting.py
+++ b/rest_framework/utils/formatting.py
@@ -2,11 +2,12 @@
Utility functions to return a formatted name and description for a given view.
"""
from __future__ import unicode_literals
+import re
from django.utils.html import escape
from django.utils.safestring import mark_safe
-from rest_framework.compat import apply_markdown
-import re
+
+from rest_framework.compat import apply_markdown, force_text
def remove_trailing_string(content, trailing):
@@ -28,6 +29,7 @@ def dedent(content):
as it fails to dedent multiline docstrings that include
unindented text on the initial line.
"""
+ content = force_text(content)
whitespace_counts = [len(line) - len(line.lstrip(' '))
for line in content.splitlines()[1:] if line.lstrip()]
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 079e9285..3b7b1c16 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -108,7 +108,9 @@ class APIView(View):
"""
view = super(APIView, cls).as_view(**initkwargs)
view.cls = cls
- return view
+ # Note: session based authentication is explicitly CSRF validated,
+ # all other authentication is CSRF exempt.
+ return csrf_exempt(view)
@property
def allowed_methods(self):
@@ -376,9 +378,9 @@ class APIView(View):
response.exception = True
return response
- # Note: session based authentication is explicitly CSRF validated,
- # all other authentication is CSRF exempt.
- @csrf_exempt
+ # Note: Views are made CSRF exempt from within `as_view` as to prevent
+ # accidental removal of this exemption in cases where `dispatch` needs to
+ # be overridden.
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,