aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework
diff options
context:
space:
mode:
Diffstat (limited to 'djangorestframework')
-rw-r--r--djangorestframework/mixins.py2
-rw-r--r--djangorestframework/permissions.py52
-rw-r--r--djangorestframework/response.py2
-rw-r--r--djangorestframework/static/css/djangorestframework.css57
-rw-r--r--djangorestframework/templates/api_login.html90
-rw-r--r--djangorestframework/tests/mixins.py9
6 files changed, 158 insertions, 54 deletions
diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py
index 43dce870..cf746839 100644
--- a/djangorestframework/mixins.py
+++ b/djangorestframework/mixins.py
@@ -499,7 +499,7 @@ class PaginatorMixin(object):
Constructs a url used for getting the next/previous urls
"""
url = URLObject.parse(self.request.get_full_path())
- url = url.add_query_param('page', page_number)
+ url = url.set_query_param('page', page_number)
limit = self.get_limit()
if limit != self.limit:
diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py
index aa4cd631..335a7213 100644
--- a/djangorestframework/permissions.py
+++ b/djangorestframework/permissions.py
@@ -20,6 +20,8 @@ __all__ = (
'PerResourceThrottling'
)
+SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
+
_403_FORBIDDEN_RESPONSE = ImmediateResponse(
{'detail': 'You do not have permission to access this resource. ' +
@@ -84,8 +86,54 @@ class IsUserOrIsAnonReadOnly(BasePermission):
def check_permission(self, user):
if (not user.is_authenticated() and
- self.view.method != 'GET' and
- self.view.method != 'HEAD'):
+ self.view.method not in SAFE_METHODS):
+ raise _403_FORBIDDEN_RESPONSE
+
+
+class DjangoModelPermissions(BasePermission):
+ """
+ The request is authenticated using `django.contrib.auth` permissions.
+ See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions
+
+ It ensures that the user is authenticated, and has the appropriate
+ `add`/`change`/`delete` permissions on the model.
+
+ This permission should only be used on views with a `ModelResource`.
+ """
+
+ # Map methods into required permission codes.
+ # Override this if you need to also provide 'read' permissions,
+ # or if you want to provide custom permission codes.
+ perms_map = {
+ 'GET': [],
+ 'OPTIONS': [],
+ 'HEAD': [],
+ 'POST': ['%(app_label)s.add_%(model_name)s'],
+ 'PUT': ['%(app_label)s.change_%(model_name)s'],
+ 'PATCH': ['%(app_label)s.change_%(model_name)s'],
+ 'DELETE': ['%(app_label)s.delete_%(model_name)s'],
+ }
+
+ def get_required_permissions(self, method, model_cls):
+ """
+ Given a model and an HTTP method, return the list of permission
+ codes that the user is required to have.
+ """
+ kwargs = {
+ 'app_label': model_cls._meta.app_label,
+ 'model_name': model_cls._meta.module_name
+ }
+ try:
+ return [perm % kwargs for perm in self.perms_map[method]]
+ except KeyError:
+ ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED)
+
+ def check_permission(self, user):
+ method = self.view.method
+ model_cls = self.view.resource.model
+ perms = self.get_required_permissions(method, model_cls)
+
+ if not user.is_authenticated or not user.has_perms(perms):
raise _403_FORBIDDEN_RESPONSE
diff --git a/djangorestframework/response.py b/djangorestframework/response.py
index 6c42c898..be2c3ebe 100644
--- a/djangorestframework/response.py
+++ b/djangorestframework/response.py
@@ -162,7 +162,7 @@ class Response(SimpleTemplateResponse):
return self.renderers[0]
-class ImmediateResponse(Response, BaseException):
+class ImmediateResponse(Response, Exception):
"""
A subclass of :class:`Response` used to abort the current request handling.
"""
diff --git a/djangorestframework/static/css/djangorestframework.css b/djangorestframework/static/css/djangorestframework.css
index 8fc4bace..1e75b8e8 100644
--- a/djangorestframework/static/css/djangorestframework.css
+++ b/djangorestframework/static/css/djangorestframework.css
@@ -1129,6 +1129,58 @@ fieldset.monospace textarea {
float: right;
}
+body.login {
+ background: #eee;
+}
+
+.login #container {
+ background: white;
+ border: 1px solid #ccc;
+ width: 28em;
+ min-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 100px;
+}
+
+.login #content-main {
+ width: 100%;
+}
+
+.login form {
+ margin-top: 1em;
+}
+
+.login .form-row {
+ padding: 4px 0;
+ float: left;
+ width: 100%;
+}
+
+.login .form-row label {
+ float: left;
+ width: 9em;
+ padding-right: 0.5em;
+ line-height: 2em;
+ text-align: right;
+ font-size: 1em;
+ color: #333;
+}
+
+.login .form-row #id_username, .login .form-row #id_password {
+ width: 14em;
+}
+
+.login span.help {
+ font-size: 10px;
+ display: block;
+}
+
+.login .submit-row {
+ clear: both;
+ padding: 1em 0 0 9.4em;
+}
+
/* Overrides specific to REST framework */
#site-name a {
@@ -1147,6 +1199,11 @@ fieldset.monospace textarea {
}
/* Custom styles */
+
.version {
font-size: 8px;
}
+
+.form-row {
+ border-bottom: 0.25em !important;
+}
diff --git a/djangorestframework/templates/api_login.html b/djangorestframework/templates/api_login.html
index 750f898b..016a4e10 100644
--- a/djangorestframework/templates/api_login.html
+++ b/djangorestframework/templates/api_login.html
@@ -1,54 +1,44 @@
+{% load static %}
<html>
- <head>
- {% if ADMIN_MEDIA_PREFIX %}
- <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/base.css'/>
- <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/forms.css'/>
- <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/login.css' />
- {% else %}
- <link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/base.css'/>
- <link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/forms.css'/>
- <link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/login.css' />
- {% endif %}
- <style>
- .form-row {border-bottom: 0.25em !important}</style>
- </head>
- <body class="login">
-<div id="container">
- <div id="header">
- <div id="branding">
- <h1 id="site-name">Django REST framework</h1>
- </div>
- </div>
-
-
-<div id="content" class="colM">
-
-<div id="content-main">
-<form method="post" action="{% url djangorestframework.utils.staticviews.api_login %}" id="login-form">
-{% csrf_token %}
- <div class="form-row">
- <label for="id_username">Username:</label> {{ form.username }}
- </div>
- <div class="form-row">
- <label for="id_password">Password:</label> {{ form.password }}
- <input type="hidden" name="next" value="{{ next }}" />
- </div>
- <div class="form-row">
- <label>&nbsp;</label><input type="submit" value="Log in">
- </div>
-</form>
-<script type="text/javascript">
-document.getElementById('id_username').focus()
-</script>
-</div>
+ <head>
+ <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}css/djangorestframework.css'/>
+ </head>
+
+ <body class="login">
+
+ <div id="container">
+
+ <div id="header">
+ <div id="branding">
+ <h1 id="site-name">Django REST framework</h1>
+ </div>
+ </div>
+
+ <div id="content" class="colM">
+ <div id="content-main">
+ <form method="post" action="{% url djangorestframework.utils.staticviews.api_login %}" id="login-form">
+ {% csrf_token %}
+ <div class="form-row">
+ <label for="id_username">Username:</label> {{ form.username }}
+ </div>
+ <div class="form-row">
+ <label for="id_password">Password:</label> {{ form.password }}
+ <input type="hidden" name="next" value="{{ next }}" />
+ </div>
+ <div class="form-row">
+ <label>&nbsp;</label><input type="submit" value="Log in">
+ </div>
+ </form>
+ <script type="text/javascript">
+ document.getElementById('id_username').focus()
+ </script>
+ </div>
+ <br class="clear">
+ </div>
+
+ <div id="footer"></div>
-
- <br class="clear">
- </div>
-
- <div id="footer"></div>
-
-</div>
-</body>
+ </div>
+ </body>
</html>
diff --git a/djangorestframework/tests/mixins.py b/djangorestframework/tests/mixins.py
index 3f5835aa..85c95d61 100644
--- a/djangorestframework/tests/mixins.py
+++ b/djangorestframework/tests/mixins.py
@@ -275,3 +275,12 @@ class TestPagination(TestCase):
self.assertTrue('foo=bar' in content['next'])
self.assertTrue('another=something' in content['next'])
self.assertTrue('page=2' in content['next'])
+
+ def test_duplicate_parameters_are_not_created(self):
+ """ Regression: ensure duplicate "page" parameters are not added to
+ paginated URLs. So page 1 should contain ?page=2, not ?page=1&page=2 """
+ request = self.req.get('/paginator/?page=1')
+ response = MockPaginatorView.as_view()(request)
+ content = json.loads(response.content)
+ self.assertTrue('page=2' in content['next'])
+ self.assertFalse('page=1' in content['next'])