diff options
| author | Sébastien Piquemal | 2012-02-14 10:10:04 +0200 |
|---|---|---|
| committer | Sébastien Piquemal | 2012-02-14 10:10:04 +0200 |
| commit | c04cb5145c4398cfac090ca7eef032296a04446f (patch) | |
| tree | f556b30b0af21980d599bd716a82cfc997679a6f | |
| parent | 821844bb11e5262fb0dfc2fecf2add8fe18d3210 (diff) | |
| parent | ba1e3b46998254e12578d75428669751e105735b (diff) | |
| download | django-rest-framework-c04cb5145c4398cfac090ca7eef032296a04446f.tar.bz2 | |
merged with trunk
| -rw-r--r-- | AUTHORS | 1 | ||||
| -rw-r--r-- | CHANGELOG.rst | 4 | ||||
| -rw-r--r-- | MANIFEST.in | 4 | ||||
| -rw-r--r-- | README.rst | 7 | ||||
| -rw-r--r-- | djangorestframework/mixins.py | 2 | ||||
| -rw-r--r-- | djangorestframework/permissions.py | 52 | ||||
| -rw-r--r-- | djangorestframework/response.py | 2 | ||||
| -rw-r--r-- | djangorestframework/static/css/djangorestframework.css | 57 | ||||
| -rw-r--r-- | djangorestframework/templates/api_login.html | 90 | ||||
| -rw-r--r-- | djangorestframework/tests/mixins.py | 9 | ||||
| -rw-r--r-- | examples/settings.py | 13 | ||||
| -rw-r--r-- | examples/urls.py | 9 | ||||
| -rwxr-xr-x[-rw-r--r--] | setup.py | 81 |
13 files changed, 241 insertions, 90 deletions
@@ -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 @@ -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> </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> </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()), @@ -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', |
