aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSébastien Piquemal2012-02-14 10:10:04 +0200
committerSébastien Piquemal2012-02-14 10:10:04 +0200
commitc04cb5145c4398cfac090ca7eef032296a04446f (patch)
treef556b30b0af21980d599bd716a82cfc997679a6f
parent821844bb11e5262fb0dfc2fecf2add8fe18d3210 (diff)
parentba1e3b46998254e12578d75428669751e105735b (diff)
downloaddjango-rest-framework-c04cb5145c4398cfac090ca7eef032296a04446f.tar.bz2
merged with trunk
-rw-r--r--AUTHORS1
-rw-r--r--CHANGELOG.rst4
-rw-r--r--MANIFEST.in4
-rw-r--r--README.rst7
-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
-rw-r--r--examples/settings.py13
-rw-r--r--examples/urls.py9
-rwxr-xr-x[-rw-r--r--]setup.py81
13 files changed, 241 insertions, 90 deletions
diff --git a/AUTHORS b/AUTHORS
index 2466de04..7fae4578 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -29,6 +29,7 @@ Benoit C <dzen>
Chris Pickett <bunchesofdonald>
Ben Timby <btimby>
Michele Lazzeri <michelelazzeri-nextage>
+Camille Harang <mammique>
THANKS TO:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 9da9df2e..010bf6c0 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,11 +4,13 @@ Release Notes
development
-----------
-* Saner template variable autoescaping.
+* Added DjangoModelPermissions class to support `django.contrib.auth` style permissions.
* Use `staticfiles` for css files.
- Easier to override. Won't conflict with customised admin styles (eg grappelli)
* Drop implied 'pk' filter if last arg in urlconf is unnamed.
- Too magical. Explict is better than implicit.
+* Saner template variable autoescaping.
+* Tider setup.py
* Bugfixes:
- Bug with PerUserThrottling when user contains unicode chars.
diff --git a/MANIFEST.in b/MANIFEST.in
index fc9ce976..5c6a1c57 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,5 @@
-recursive-include djangorestframework/static *.ico *.txt
+recursive-include djangorestframework/static *.ico *.txt *.css
recursive-include djangorestframework/templates *.txt *.html
recursive-include examples .keep *.py *.txt
recursive-include docs *.py *.rst *.html *.txt
-include AUTHORS LICENSE requirements.txt tox.ini
+include AUTHORS LICENSE CHANGELOG.rst requirements.txt tox.ini
diff --git a/README.rst b/README.rst
index 9cec01cf..23a8075e 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,12 @@
Django REST framework
=====================
-Django REST framework makes it easy to build well-connected, self-describing RESTful Web APIs.
+**Django REST framework makes it easy to build well-connected, self-describing RESTful Web APIs.**
+
+**Author:** Tom Christie. `Follow me on Twitter <https://twitter.com/_tomchristie>`_.
+
+Overview
+========
Features:
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'])
diff --git a/examples/settings.py b/examples/settings.py
index 082ba9d3..d7da6b0a 100644
--- a/examples/settings.py
+++ b/examples/settings.py
@@ -1,4 +1,5 @@
# Settings for djangorestframework examples project
+import django
import os
DEBUG = True
@@ -84,19 +85,17 @@ TEMPLATE_DIRS = (
# Don't forget to use absolute paths, not relative paths.
)
-# for loading initial data
-##SERIALIZATION_MODULES = {
- # 'yml': "django.core.serializers.pyyaml"
-
-#}
-
+if django.VERSION < (1, 3):
+ staticfiles = 'staticfiles'
+else:
+ staticfiles = 'django.contrib.staticfiles'
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
- 'django.contrib.staticfiles',
+ staticfiles,
'django.contrib.messages',
'djangorestframework',
diff --git a/examples/urls.py b/examples/urls.py
index 402fde28..f246828a 100644
--- a/examples/urls.py
+++ b/examples/urls.py
@@ -1,7 +1,10 @@
-from django.conf.urls.defaults import patterns, include, url
-from django.conf import settings
+from django.conf.urls.defaults import patterns, include
from sandbox.views import Sandbox
-from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+try:
+ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+except ImportError: # Django <= 1.2
+ from staticfiles.urls import staticfiles_urlpatterns
+
urlpatterns = patterns('',
(r'^$', Sandbox.as_view()),
diff --git a/setup.py b/setup.py
index 690a7e0f..5cd2a28f 100644..100755
--- a/setup.py
+++ b/setup.py
@@ -1,33 +1,70 @@
-#!/usr/bin/env/python
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup
+import re
+import os
+import sys
-import os, re
-path = os.path.join(os.path.dirname(__file__), 'djangorestframework', '__init__.py')
-init_py = open(path).read()
-VERSION = re.match("__version__ = '([^']+)'", init_py).group(1)
+def get_version(package):
+ """
+ Return package version as listed in `__version__` in `init.py`.
+ """
+ init_py = open(os.path.join(package, '__init__.py')).read()
+ return re.match("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1)
+
+
+def get_packages(package):
+ """
+ Return root package and all sub-packages.
+ """
+ return [dirpath
+ for dirpath, dirnames, filenames in os.walk(package)
+ if os.path.exists(os.path.join(dirpath, '__init__.py'))]
+
+
+def get_package_data(package):
+ """
+ Return all files under the root package, that are not in a
+ package themselves.
+ """
+ walk = [(dirpath.replace(package + os.sep, '', 1), filenames)
+ for dirpath, dirnames, filenames in os.walk(package)
+ if not os.path.exists(os.path.join(dirpath, '__init__.py'))]
+
+ filepaths = []
+ for base, filenames in walk:
+ filepaths.extend([os.path.join(base, filename)
+ for filename in filenames])
+ return {package: filepaths}
+
+
+version = get_version('djangorestframework')
+
+
+if sys.argv[-1] == 'publish':
+ os.system("python setup.py sdist upload")
+ print "You probably want to also tag the version now:"
+ print " git tag -a %s -m 'version %s'" % (version, version)
+ print " git push --tags"
+ sys.exit()
+
setup(
- name = 'djangorestframework',
- version = VERSION,
- url = 'http://django-rest-framework.org',
- download_url = 'http://pypi.python.org/pypi/djangorestframework/',
- license = 'BSD',
- description = 'A lightweight REST framework for Django.',
- author = 'Tom Christie',
- author_email = 'tom@tomchristie.com',
- packages = ['djangorestframework',
- 'djangorestframework.templatetags',
- 'djangorestframework.tests',
- 'djangorestframework.runtests',
- 'djangorestframework.utils'],
- package_dir={'djangorestframework': 'djangorestframework'},
- package_data = {'djangorestframework': ['templates/*', 'static/*']},
- test_suite = 'djangorestframework.runtests.runcoverage.main',
+ name='djangorestframework',
+ version=version,
+ url='http://django-rest-framework.org',
+ download_url='http://pypi.python.org/pypi/djangorestframework/',
+ license='BSD',
+ description='A lightweight REST framework for Django.',
+ author='Tom Christie',
+ author_email='tom@tomchristie.com',
+ packages=get_packages('djangorestframework'),
+ package_data=get_package_data('djangorestframework'),
+ test_suite='djangorestframework.runtests.runcoverage.main',
install_requires=['URLObject>=0.6.0'],
- classifiers = [
+ classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Framework :: Django',