aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/authentication.py2
-rw-r--r--rest_framework/authtoken/migrations/0001_initial.py7
-rw-r--r--rest_framework/authtoken/serializers.py2
-rw-r--r--rest_framework/fields.py10
-rw-r--r--rest_framework/filters.py1
-rw-r--r--rest_framework/generics.py18
-rw-r--r--rest_framework/pagination.py9
-rw-r--r--rest_framework/parsers.py20
-rw-r--r--rest_framework/permissions.py3
-rw-r--r--rest_framework/routers.py24
-rw-r--r--rest_framework/serializers.py28
-rw-r--r--rest_framework/static/rest_framework/css/bootstrap-tweaks.css127
-rw-r--r--rest_framework/static/rest_framework/css/default.css40
-rw-r--r--rest_framework/templates/rest_framework/base.html430
-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
-rw-r--r--rest_framework/viewsets.py3
22 files changed, 468 insertions, 334 deletions
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index f30012b9..7f724c18 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
-__version__ = '2.3.14'
+__version__ = '2.4.3'
__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/authtoken/migrations/0001_initial.py b/rest_framework/authtoken/migrations/0001_initial.py
index 2e5d6b47..769f6202 100644
--- a/rest_framework/authtoken/migrations/0001_initial.py
+++ b/rest_framework/authtoken/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# encoding: utf8
+# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
@@ -15,12 +15,11 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Token',
fields=[
- ('key', models.CharField(max_length=40, serialize=False, primary_key=True)),
- ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, to_field='id')),
+ ('key', models.CharField(primary_key=True, serialize=False, max_length=40)),
('created', models.DateTimeField(auto_now_add=True)),
+ ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='auth_token')),
],
options={
- 'abstract': False,
},
bases=(models.Model,),
),
diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py
index 99e99ae3..472e59ee 100644
--- a/rest_framework/authtoken/serializers.py
+++ b/rest_framework/authtoken/serializers.py
@@ -22,7 +22,7 @@ class AuthTokenSerializer(serializers.Serializer):
attrs['user'] = user
return attrs
else:
- msg = _('Unable to login with provided credentials.')
+ msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "username" and "password"')
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 9d707c9b..c0253f86 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -475,8 +475,12 @@ class CharField(WritableField):
if isinstance(value, six.string_types):
return value
- if value is None and not self.allow_none:
- return ''
+ if value is None:
+ if not self.allow_none:
+ return ''
+ else:
+ # Return None explicitly because smart_text(None) == 'None'. See #1834 for details
+ return None
return smart_text(value)
@@ -559,7 +563,7 @@ 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_text(k2):
+ if value == smart_text(k2) or value == k2:
return True
else:
if value == smart_text(k) or value == k:
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/generics.py b/rest_framework/generics.py
index 77deb8e4..a62da00b 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -252,6 +252,12 @@ class GenericAPIView(views.APIView):
if serializer_class is not None:
return serializer_class
+ warnings.warn(
+ 'The `.model` attribute on view classes is now deprecated in favor '
+ 'of the more explicit `serializer_class` and `queryset` attributes.',
+ DeprecationWarning, stacklevel=2
+ )
+
assert self.model is not None, \
"'%s' should either include a 'serializer_class' attribute, " \
"or use the 'model' attribute as a shortcut for " \
@@ -282,6 +288,11 @@ class GenericAPIView(views.APIView):
return self.queryset._clone()
if self.model is not None:
+ warnings.warn(
+ 'The `.model` attribute on view classes is now deprecated in favor '
+ 'of the more explicit `serializer_class` and `queryset` attributes.',
+ DeprecationWarning, stacklevel=2
+ )
return self.model._default_manager.all()
error_format = "'%s' must define 'queryset' or 'model'"
@@ -387,10 +398,11 @@ class GenericAPIView(views.APIView):
if method not in self.allowed_methods:
continue
- cloned_request = clone_request(request, method)
+ original_request = self.request
+ self.request = clone_request(request, method)
try:
# Test global permissions
- self.check_permissions(cloned_request)
+ self.check_permissions(self.request)
# Test object permissions
if method == 'PUT':
try:
@@ -408,6 +420,8 @@ class GenericAPIView(views.APIView):
# appropriate metadata about the fields that should be supplied.
serializer = self.get_serializer()
actions[method] = serializer.metadata()
+ finally:
+ self.request = original_request
if actions:
ret['actions'] = actions
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index d51ea929..1f5749f1 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)
@@ -82,7 +83,9 @@ class BasePaginationSerializer(serializers.Serializer):
else:
context_kwarg = {}
- self.fields[results_field] = object_serializer(source='object_list', **context_kwarg)
+ self.fields[results_field] = object_serializer(source='object_list',
+ many=True,
+ **context_kwarg)
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/permissions.py b/rest_framework/permissions.py
index 6a1a0077..29f60d6d 100644
--- a/rest_framework/permissions.py
+++ b/rest_framework/permissions.py
@@ -108,6 +108,9 @@ class DjangoModelPermissions(BasePermission):
return [perm % kwargs for perm in self.perms_map[method]]
def has_permission(self, request, view):
+ # Note that `.model` attribute on views is deprecated, although we
+ # enforce the deprecation on the view `get_serializer_class()` and
+ # `get_queryset()` methods, rather than here.
model_cls = getattr(view, 'model', None)
queryset = getattr(view, 'queryset', None)
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index 406ebcf7..169e6e8b 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -19,6 +19,8 @@ import itertools
from collections import namedtuple
from django.conf.urls import patterns, url
from django.core.exceptions import ImproperlyConfigured
+from django.core.urlresolvers import NoReverseMatch
+from django.utils.datastructures import SortedDict
from rest_framework import views
from rest_framework.response import Response
from rest_framework.reverse import reverse
@@ -128,6 +130,9 @@ class SimpleRouter(BaseRouter):
If `base_name` is not specified, attempt to automatically determine
it from the viewset.
"""
+ # Note that `.model` attribute on views is deprecated, although we
+ # enforce the deprecation on the view `get_serializer_class()` and
+ # `get_queryset()` methods, rather than here.
model_cls = getattr(viewset, 'model', None)
queryset = getattr(viewset, 'queryset', None)
if model_cls is None and queryset is not None:
@@ -135,7 +140,7 @@ class SimpleRouter(BaseRouter):
assert model_cls, '`base_name` argument not specified, and could ' \
'not automatically determine the name from the viewset, as ' \
- 'it does not have a `.model` or `.queryset` attribute.'
+ 'it does not have a `.queryset` attribute.'
return model_cls._meta.object_name.lower()
@@ -273,7 +278,7 @@ class DefaultRouter(SimpleRouter):
"""
Return a view to use as the API root.
"""
- api_root_dict = {}
+ api_root_dict = SortedDict()
list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
api_root_dict[prefix] = list_name.format(basename=basename)
@@ -281,10 +286,19 @@ class DefaultRouter(SimpleRouter):
class APIRoot(views.APIView):
_ignore_model_permissions = True
- def get(self, request, format=None):
- ret = {}
+ def get(self, request, *args, **kwargs):
+ ret = SortedDict()
for key, url_name in api_root_dict.items():
- ret[key] = reverse(url_name, request=request, format=format)
+ try:
+ ret[key] = reverse(
+ url_name,
+ request=request,
+ format=kwargs.get('format', None)
+ )
+ except NoReverseMatch:
+ # Don't bail out if eg. no list routes exist, only detail routes.
+ continue
+
return Response(ret)
return APIRoot.as_view()
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 27af7ef3..d24cb99d 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -625,6 +625,20 @@ class ModelSerializerOptions(SerializerOptions):
self.write_only_fields = getattr(meta, 'write_only_fields', ())
+def _get_class_mapping(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):
"""
A serializer that deals with model instances and querysets.
@@ -899,15 +913,17 @@ class ModelSerializer(Serializer):
models.URLField: ['max_length'],
}
- if model_field.__class__ in attribute_dict:
- attributes = attribute_dict[model_field.__class__]
+ attributes = _get_class_mapping(attribute_dict, model_field)
+ if attributes:
for attribute in attributes:
kwargs.update({attribute: getattr(model_field, attribute)})
- try:
- return self.field_mapping[model_field.__class__](**kwargs)
- except KeyError:
- return ModelField(model_field=model_field, **kwargs)
+ serializer_field_class = _get_class_mapping(
+ self.field_mapping, model_field)
+
+ if serializer_field_class:
+ return serializer_field_class(**kwargs)
+ return ModelField(model_field=model_field, **kwargs)
def get_validation_exclusions(self, instance=None):
"""
diff --git a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
index 6bfb778c..6fa1e6cb 100644
--- a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
+++ b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
@@ -6,30 +6,30 @@ a single block in the template.
*/
-
.form-actions {
- background: transparent;
- border-top-color: transparent;
- padding-top: 0;
+ background: transparent;
+ border-top-color: transparent;
+ padding-top: 0;
}
.navbar-inverse .brand a {
- color: #999;
+ color: #999999;
}
.navbar-inverse .brand:hover a {
- color: white;
- text-decoration: none;
+ color: white;
+ text-decoration: none;
}
/* custom navigation styles */
-.wrapper .navbar{
+.navbar {
width: 100%;
- position: absolute;
+ position: fixed;
left: 0;
top: 0;
+ z-index: 3;
}
-.navbar .navbar-inner{
+.navbar .navbar-inner {
background: #2C2C2C;
color: white;
border: none;
@@ -37,25 +37,26 @@ a single block in the template.
border-radius: 0px;
}
-.navbar .navbar-inner .nav li, .navbar .navbar-inner .nav li a, .navbar .navbar-inner .brand:hover{
+.navbar .navbar-inner .nav li, .navbar .navbar-inner .nav li a, .navbar .navbar-inner .brand:hover {
color: white;
}
.nav-list > .active > a, .nav-list > .active > a:hover {
- background: #2c2c2c;
+ background: #2C2C2C;
}
-.navbar .navbar-inner .dropdown-menu li a, .navbar .navbar-inner .dropdown-menu li{
- color: #A30000;
+.navbar .navbar-inner .dropdown-menu li a, .navbar .navbar-inner .dropdown-menu li {
+ color: #A30000;
}
-.navbar .navbar-inner .dropdown-menu li a:hover{
- background: #eeeeee;
- color: #c20000;
+
+.navbar .navbar-inner .dropdown-menu li a:hover {
+ background: #EEEEEE;
+ color: #C20000;
}
/*=== dabapps bootstrap styles ====*/
-html{
+html {
width:100%;
background: none;
}
@@ -65,121 +66,127 @@ body, .navbar .navbar-inner .container-fluid {
margin: 0 auto;
}
-body{
+body {
background: url("../img/grid.png") repeat-x;
background-attachment: fixed;
}
-#content{
- margin: 0;
+#content {
+ margin: 0;
+ padding-bottom: 60px;
}
/* sticky footer and footer */
html, body {
height: 100%;
}
+
.wrapper {
+ position: relative;
+ top: 0;
+ left: 0;
+ padding-top: 60px;
+ margin: -60px 0;
min-height: 100%;
- height: auto !important;
- height: 100%;
- margin: 0 auto -60px;
}
.form-switcher {
- margin-bottom: 0;
+ margin-bottom: 0;
}
.well {
- -webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
}
.well .form-actions {
- padding-bottom: 0;
- margin-bottom: 0;
+ padding-bottom: 0;
+ margin-bottom: 0;
}
.well form {
- margin-bottom: 0;
+ margin-bottom: 0;
}
.well form .help-block {
- color: #999;
+ color: #999999;
}
.nav-tabs {
- border: 0;
+ border: 0;
}
.nav-tabs > li {
- float: right;
+ float: right;
}
.nav-tabs li a {
- margin-right: 0;
+ margin-right: 0;
}
.nav-tabs > .active > a {
- background: #f5f5f5;
+ background: #F5F5F5;
}
.nav-tabs > .active > a:hover {
- background: #f5f5f5;
-}
-
-.tabbable.first-tab-active .tab-content
-{
- border-top-right-radius: 0;
+ background: #F5F5F5;
}
-#footer, #push {
- height: 60px; /* .push must be the same height as .footer */
+.tabbable.first-tab-active .tab-content {
+ border-top-right-radius: 0;
}
-#footer{
- text-align: right;
+footer {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ clear: both;
+ z-index: 10;
+ height: 60px;
+ width: 95%;
+ margin: 0 2.5%;
}
-#footer p {
+footer p {
text-align: center;
color: gray;
- border-top: 1px solid #DDD;
+ border-top: 1px solid #DDDDDD;
padding-top: 10px;
}
-#footer a {
- color: gray;
+footer a {
+ color: gray !important;
font-weight: bold;
}
-#footer a:hover {
+footer a:hover {
color: gray;
}
.page-header {
- border-bottom: none;
- padding-bottom: 0px;
- margin-bottom: 20px;
+ border-bottom: none;
+ padding-bottom: 0px;
+ margin-bottom: 20px;
}
/* custom general page styles */
-.hero-unit h2, .hero-unit h1{
+.hero-unit h1, .hero-unit h2 {
color: #A30000;
}
-body a, body a{
+body a {
color: #A30000;
}
-body a:hover{
+body a:hover {
color: #c20000;
}
-#content a span{
- text-decoration: underline;
+#content a span {
+ text-decoration: underline;
}
.request-info {
- clear:both;
+ clear:both;
}
diff --git a/rest_framework/static/rest_framework/css/default.css b/rest_framework/static/rest_framework/css/default.css
index 0261a303..461cdfe5 100644
--- a/rest_framework/static/rest_framework/css/default.css
+++ b/rest_framework/static/rest_framework/css/default.css
@@ -3,20 +3,20 @@
content running up underneath it. */
h1 {
- font-weight: 500;
+ font-weight: 500;
}
h2, h3 {
- font-weight: 300;
+ font-weight: 300;
}
.resource-description, .response-info {
- margin-bottom: 2em;
+ margin-bottom: 2em;
}
.version:before {
- content: "v";
- opacity: 0.6;
- padding-right: 0.25em;
+ content: "v";
+ opacity: 0.6;
+ padding-right: 0.25em;
}
.version {
@@ -24,16 +24,16 @@ h2, h3 {
}
.format-option {
- font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace;
+ font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace;
}
.button-form {
- float: right;
- margin-right: 1em;
+ float: right;
+ margin-right: 1em;
}
ul.breadcrumb {
- margin: 58px 0 0 0;
+ margin: 80px 0 0 0;
}
form select, form input, form textarea {
@@ -43,17 +43,18 @@ form select, form input, form textarea {
form select[multiple] {
height: 150px;
}
+
/* To allow tooltips to work on disabled elements */
.disabled-tooltip-shield {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
}
.errorlist {
- margin-top: 0.5em;
+ margin-top: 0.5em;
}
pre {
@@ -64,8 +65,7 @@ pre {
}
.page-header {
- border-bottom: none;
- padding-bottom: 0px;
- margin-bottom: 20px;
+ border-bottom: none;
+ padding-bottom: 0px;
+ margin-bottom: 20px;
}
-
diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html
index b6e9ca5c..3628daa0 100644
--- a/rest_framework/templates/rest_framework/base.html
+++ b/rest_framework/templates/rest_framework/base.html
@@ -4,233 +4,255 @@
<!DOCTYPE html>
<html>
<head>
- {% block head %}
+ {% block head %}
- {% block meta %}
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
- <meta name="robots" content="NONE,NOARCHIVE" />
- {% endblock %}
+ {% 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>
+ <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" %}"/>
- <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap-tweaks.css" %}"/>
- {% endblock %}
- <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 %}
+ {% block style %}
+ {% block bootstrap_theme %}
+ <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/>
+ <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap-tweaks.css" %}"/>
+ {% endblock %}
+ <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 %}
+ {% endblock %}
</head>
- {% block body %}
- <body class="{% block bodyclass %}{% endblock %} container">
-
- <div class="wrapper">
-
- {% block navbar %}
- <div class="navbar {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}">
- <div class="navbar-inner">
- <div class="container-fluid">
- <span href="/">
- {% block branding %}<a class='brand' rel="nofollow" href='http://www.django-rest-framework.org'>Django REST framework <span class="version">{{ version }}</span></a>{% endblock %}
- </span>
- <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>
- {% else %}
- <li>{% optional_login request %}</li>
- {% endif %}
- {% endblock %}
- </ul>
- </div>
- </div>
- </div>
- {% endblock %}
+ {% block body %}
+ <body class="{% block bodyclass %}{% endblock %} container">
- {% block breadcrumbs %}
- <ul class="breadcrumb">
- {% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
- <li>
- <a href="{{ breadcrumb_url }}" {% if forloop.last %}class="active"{% endif %}>{{ breadcrumb_name }}</a> {% if not forloop.last %}<span class="divider">&rsaquo;</span>{% endif %}
- </li>
- {% endfor %}
- </ul>
- {% endblock %}
+ <div class="wrapper">
- <!-- Content -->
- <div id="content">
-
- {% if 'GET' in allowed_methods %}
- <form id="get-form" class="pull-right">
- <fieldset>
- <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>
- </button>
- <ul class="dropdown-menu">
- {% for format in available_formats %}
- <li>
- <a class="js-tooltip format-option" href='{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}' rel="nofollow" title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a>
- </li>
- {% endfor %}
- </ul>
+ {% block navbar %}
+ <div class="navbar {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}">
+ <div class="navbar-inner">
+ <div class="container-fluid">
+ <span>
+ {% block branding %}
+ <a class='brand' rel="nofollow" href='http://www.django-rest-framework.org'>
+ Django REST framework <span class="version">{{ version }}</span>
+ </a>
+ {% endblock %}
+ </span>
+ <ul class="nav pull-right">
+ {% block userlinks %}
+ {% if user.is_authenticated %}
+ {% optional_logout request user %}
+ {% else %}
+ {% optional_login request %}
+ {% endif %}
+ {% endblock %}
+ </ul>
+ </div>
</div>
+ </div>
+ {% endblock %}
- </fieldset>
- </form>
- {% endif %}
-
- {% if options_form %}
- <form class="button-form" action="{{ request.get_full_path }}" method="POST">
- {% csrf_token %}
- <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="OPTIONS" />
- <button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button>
- </form>
- {% endif %}
-
- {% if delete_form %}
- <form class="button-form" action="{{ request.get_full_path }}" method="POST">
- {% csrf_token %}
- <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" />
- <button class="btn btn-danger js-tooltip" title="Make a DELETE request on the {{ name }} resource">DELETE</button>
- </form>
- {% endif %}
-
- <div class="content-main">
- <div class="page-header"><h1>{{ name }}</h1></div>
- {% block description %}
- {{ description }}
+ {% block breadcrumbs %}
+ <ul class="breadcrumb">
+ {% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
+ <li>
+ <a href="{{ breadcrumb_url }}" {% if forloop.last %}class="active"{% endif %}>
+ {{ breadcrumb_name }}
+ </a>
+ {% if not forloop.last %}<span class="divider">&rsaquo;</span>{% endif %}
+ </li>
+ {% endfor %}
+ </ul>
{% endblock %}
- <div class="request-info" style="clear: both" >
- <pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>
- </div>
- <div class="response-info">
- <pre class="prettyprint"><div class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
-{% for key, val in response_headers.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>
-{% endfor %}
-</div>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
- </div>
- </div>
- {% if display_edit_forms %}
+ <!-- Content -->
+ <div id="content">
- {% if post_form or raw_data_post_form %}
- <div {% if post_form %}class="tabbable"{% endif %}>
- {% if post_form %}
- <ul class="nav nav-tabs form-switcher">
- <li><a name='html-tab' href="#object-form" data-toggle="tab">HTML form</a></li>
- <li><a name='raw-tab' href="#generic-content-form" data-toggle="tab">Raw data</a></li>
- </ul>
- {% endif %}
- <div class="well tab-content">
- {% if post_form %}
- <div class="tab-pane" id="object-form">
- {% with form=post_form %}
- <form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal">
- <fieldset>
- {{ post_form }}
- <div class="form-actions">
- <button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
- </div>
- </fieldset>
- </form>
- {% endwith %}
- </div>
- {% endif %}
- <div {% if post_form %}class="tab-pane"{% endif %} id="generic-content-form">
- {% with form=raw_data_post_form %}
- <form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
- <fieldset>
- {% include "rest_framework/raw_data_form.html" %}
- <div class="form-actions">
- <button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
- </div>
- </fieldset>
- </form>
- {% endwith %}
- </div>
+ {% if 'GET' in allowed_methods %}
+ <form id="get-form" class="pull-right">
+ <fieldset>
+ <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>
+ </button>
+ <ul class="dropdown-menu">
+ {% for format in available_formats %}
+ <li>
+ <a class="js-tooltip format-option"
+ href='{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}'
+ rel="nofollow"
+ title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">
+ {{ format }}
+ </a>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ </fieldset>
+ </form>
+ {% endif %}
+
+ {% if options_form %}
+ <form class="button-form" action="{{ request.get_full_path }}" method="POST">
+ {% csrf_token %}
+ <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="OPTIONS" />
+ <button class="btn btn-primary js-tooltip"
+ title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button>
+ </form>
+ {% endif %}
+
+ {% if delete_form %}
+ <form class="button-form" action="{{ request.get_full_path }}" method="POST">
+ {% csrf_token %}
+ <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" />
+ <button class="btn btn-danger js-tooltip"
+ title="Make a DELETE request on the {{ name }} resource">DELETE</button>
+ </form>
+ {% endif %}
+
+ <div class="content-main">
+ <div class="page-header">
+ <h1>{{ name }}</h1>
+ </div>
+ {% block description %}
+ {{ description }}
+ {% endblock %}
+ <div class="request-info" style="clear: both" >
+ <pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>
+ </div>
+ <div class="response-info">
+ <pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
+{% for key, val in response_headers.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>
+{% endfor %}
+</span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
</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 %}
- <ul class="nav nav-tabs form-switcher">
- <li><a name='html-tab' href="#object-form" data-toggle="tab">HTML form</a></li>
- <li><a name='raw-tab' href="#generic-content-form" data-toggle="tab">Raw data</a></li>
- </ul>
- {% endif %}
- <div class="well tab-content">
- {% if put_form %}
- <div class="tab-pane" id="object-form">
- <form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal">
- <fieldset>
- {{ put_form }}
- <div class="form-actions">
- <button class="btn btn-primary js-tooltip" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button>
+ {% if display_edit_forms %}
+
+ {% if post_form or raw_data_post_form %}
+ <div {% if post_form %}class="tabbable"{% endif %}>
+ {% if post_form %}
+ <ul class="nav nav-tabs form-switcher">
+ <li>
+ <a name='html-tab' href="#post-object-form" data-toggle="tab">HTML form</a>
+ </li>
+ <li>
+ <a name='raw-tab' href="#post-generic-content-form" data-toggle="tab">Raw data</a>
+ </li>
+ </ul>
+ {% endif %}
+ <div class="well tab-content">
+ {% if post_form %}
+ <div class="tab-pane" id="post-object-form">
+ {% with form=post_form %}
+ <form action="{{ request.get_full_path }}"
+ method="POST" enctype="multipart/form-data" class="form-horizontal">
+ <fieldset>
+ {{ post_form }}
+ <div class="form-actions">
+ <button class="btn btn-primary"
+ title="Make a POST request on the {{ name }} resource">POST</button>
+ </div>
+ </fieldset>
+ </form>
+ {% endwith %}
</div>
- </fieldset>
- </form>
+ {% endif %}
+ <div {% if post_form %}class="tab-pane"{% endif %} id="post-generic-content-form">
+ {% with form=raw_data_post_form %}
+ <form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
+ <fieldset>
+ {% include "rest_framework/raw_data_form.html" %}
+ <div class="form-actions">
+ <button class="btn btn-primary"
+ title="Make a POST request on the {{ name }} resource">POST</button>
+ </div>
+ </fieldset>
+ </form>
+ {% endwith %}
+ </div>
+ </div>
</div>
- {% endif %}
- <div {% if put_form %}class="tab-pane"{% endif %} id="generic-content-form">
- {% with form=raw_data_put_or_patch_form %}
- <form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
- <fieldset>
- {% include "rest_framework/raw_data_form.html" %}
- <div class="form-actions">
- {% if raw_data_put_form %}
- <button class="btn btn-primary js-tooltip" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button>
- {% endif %}
- {% if raw_data_patch_form %}
- <button class="btn btn-primary js-tooltip" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PATCH" title="Make a PATCH request on the {{ name }} resource">PATCH</button>
- {% endif %}
+ {% endif %}
+
+ {% if put_form or raw_data_put_form or raw_data_patch_form %}
+ <div {% if put_form %}class="tabbable"{% endif %}>
+ {% if put_form %}
+ <ul class="nav nav-tabs form-switcher">
+ <li>
+ <a name='html-tab' href="#put-object-form" data-toggle="tab">HTML form</a>
+ </li>
+ <li>
+ <a name='raw-tab' href="#put-generic-content-form" data-toggle="tab">Raw data</a>
+ </li>
+ </ul>
+ {% endif %}
+ <div class="well tab-content">
+ {% if put_form %}
+ <div class="tab-pane" id="put-object-form">
+ <form action="{{ request.get_full_path }}"
+ method="POST" enctype="multipart/form-data" class="form-horizontal">
+ <fieldset>
+ {{ put_form }}
+ <div class="form-actions">
+ <button class="btn btn-primary js-tooltip"
+ name="{{ api_settings.FORM_METHOD_OVERRIDE }}"
+ value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button>
+ </div>
+ </fieldset>
+ </form>
</div>
- </fieldset>
- </form>
- {% endwith %}
+ {% endif %}
+ <div {% if put_form %}class="tab-pane"{% endif %} id="put-generic-content-form">
+ {% with form=raw_data_put_or_patch_form %}
+ <form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
+ <fieldset>
+ {% include "rest_framework/raw_data_form.html" %}
+ <div class="form-actions">
+ {% if raw_data_put_form %}
+ <button class="btn btn-primary js-tooltip"
+ name="{{ api_settings.FORM_METHOD_OVERRIDE }}"
+ value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button>
+ {% endif %}
+ {% if raw_data_patch_form %}
+ <button class="btn btn-primary js-tooltip"
+ name="{{ api_settings.FORM_METHOD_OVERRIDE }}"
+ value="PATCH" title="Make a PATCH request on the {{ name }} resource">PATCH</button>
+ {% endif %}
+ </div>
+ </fieldset>
+ </form>
+ {% endwith %}
+ </div>
+ </div>
</div>
- </div>
- </div>
+ {% endif %}
{% endif %}
- {% endif %}
-
- </div>
- <!-- END content-main -->
-
- </div>
- <!-- END Content -->
-
- <div id="push"></div>
-
- </div>
+ </div>
+ <!-- END Content -->
- </div><!-- ./wrapper -->
+ <footer>
+ {% block footer %}
+ <p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</p>
+ {% endblock %}
+ </footer>
- {% block footer %}
- {% endblock %}
+ </div><!-- ./wrapper -->
- {% block script %}
- <script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script>
- <script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
- <script src="{% static "rest_framework/js/prettify-min.js" %}"></script>
- <script src="{% static "rest_framework/js/default.js" %}"></script>
+ {% block script %}
+ <script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script>
+ <script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
+ <script src="{% static "rest_framework/js/prettify-min.js" %}"></script>
+ <script src="{% static "rest_framework/js/default.js" %}"></script>
+ {% endblock %}
+ </body>
{% 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 23df3443..38346ab7 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -103,7 +103,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):
@@ -371,9 +373,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,
diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py
index bb5b304e..84b4bd8d 100644
--- a/rest_framework/viewsets.py
+++ b/rest_framework/viewsets.py
@@ -20,6 +20,7 @@ from __future__ import unicode_literals
from functools import update_wrapper
from django.utils.decorators import classonlymethod
+from django.views.decorators.csrf import csrf_exempt
from rest_framework import views, generics, mixins
@@ -89,7 +90,7 @@ class ViewSetMixin(object):
# resolved URL.
view.cls = cls
view.suffix = initkwargs.get('suffix', None)
- return view
+ return csrf_exempt(view)
def initialize_request(self, request, *args, **kargs):
"""