From da8187d2c293e095969c00751c21338d53c7f3f2 Mon Sep 17 00:00:00 2001 From: Camille Harang Date: Wed, 1 Feb 2012 23:56:54 +0100 Subject: * -> ** --- djangorestframework/mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'djangorestframework') diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index c7b32d2d..f4a9c998 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -608,7 +608,7 @@ class UpdateModelMixin(ModelMixin): # TODO: update on the url of a non-existing resource url doesn't work # correctly at the moment - will end up with a new url try: - self.model_instance = self.get_instance(*query_kwargs) + self.model_instance = self.get_instance(**query_kwargs) for (key, val) in self.CONTENT.items(): setattr(self.model_instance, key, val) -- cgit v1.2.3 From bd25e99f155b68e31239bf1c9e3d4e70d53bbddd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 2 Feb 2012 09:05:28 +0000 Subject: Python docs say inherit from Exception, not BaseException. --- djangorestframework/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'djangorestframework') diff --git a/djangorestframework/response.py b/djangorestframework/response.py index 96345cee..13f0477e 100644 --- a/djangorestframework/response.py +++ b/djangorestframework/response.py @@ -34,7 +34,7 @@ class Response(object): return STATUS_CODE_TEXT.get(self.status, '') -class ErrorResponse(BaseException): +class ErrorResponse(Exception): """ An exception representing an Response that should be returned immediately. Any content should be serialized as-is, without being filtered. -- cgit v1.2.3 From 15fc26f50b94d41d1024a3f40fe21af5f2d07bfb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 08:58:15 +0000 Subject: Fix up packaging and staticfiles changes. Fixes #155. Fixes #153. Fixes #150. --- .../static/css/djangorestframework.css | 57 ++++++++++++++ djangorestframework/templates/api_login.html | 90 ++++++++++------------ 2 files changed, 97 insertions(+), 50 deletions(-) (limited to 'djangorestframework') 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 %} - - {% if ADMIN_MEDIA_PREFIX %} - - - - {% else %} - - - - {% endif %} - - - -
- - - -
- -
-
-{% csrf_token %} -
- {{ form.username }} -
-
- {{ form.password }} - -
-
- -
-
- -
+ + + + + + +
+ + + +
+
+
+ {% csrf_token %} +
+ {{ form.username }} +
+
+ {{ form.password }} + +
+
+ +
+
+ +
+
+
+ + - -
-
- - - -
- +
+ -- cgit v1.2.3 From 76a7d35813b637bb199a0d388468f9265f8adaf2 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Tue, 7 Feb 2012 11:08:55 +0000 Subject: Ensure duplicate "page" parameters are not created Previously, URLObject.add_query_param was used to generate next/previous page links in PaginatorMixin. This resulted in (for example) page 2's "next" link having the params: ?page=2&page=3 Instead, URLObject.set_query_param should be used to replace the current value of the "page" parameter. --- djangorestframework/mixins.py | 2 +- djangorestframework/tests/mixins.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'djangorestframework') diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index f4a9c998..836c3a59 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -679,7 +679,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/tests/mixins.py b/djangorestframework/tests/mixins.py index a7512efc..8268fdca 100644 --- a/djangorestframework/tests/mixins.py +++ b/djangorestframework/tests/mixins.py @@ -280,3 +280,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']) -- cgit v1.2.3 From bc80eb266f071e0c090fcf882722d4dd056ccf61 Mon Sep 17 00:00:00 2001 From: Camille Harang Date: Sat, 11 Feb 2012 01:49:28 +0100 Subject: DjangoModelPermisson --- djangorestframework/permissions.py | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'djangorestframework') diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index dfe55ce9..100a976e 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -89,6 +89,46 @@ class IsUserOrIsAnonReadOnly(BasePermission): raise _403_FORBIDDEN_RESPONSE +class DjangoModelPermisson(BasePermission): + """ + """ + + def check_permission(self, user): + + # GET-style methods are always allowed. + if self.view.request.method in ('GET', 'OPTIONS', 'HEAD',): + return + + # User must be logged in to check permissions. + if not hasattr(self.view.request, 'user') or not self.view.request.user.is_authenticated(): + raise _403_FORBIDDEN_RESPONSE + + klass = self.view.resource.model + + # If it doesn't look like a model, we can't check permissions. + if not klass or not getattr(klass, '_meta', None): + return + + permission_map = { + 'POST': ['%s.add_%s'], + 'PUT': ['%s.change_%s'], + 'DELETE': ['%s.delete_%s'], + 'PATCH': ['%s.add_%s', '%s.change_%s', '%s.delete_%s'], + } + permission_codes = [] + + # If we don't recognize the HTTP method, we don't know what + # permissions to check. Deny. + if self.view.request.method not in permission_map: + raise _403_FORBIDDEN_RESPONSE + + for perm in permission_map[self.view.request.method]: + permission_codes.append(perm % (klass._meta.app_label, klass._meta.module_name)) + + if not self.view.request.user.has_perms(permission_codes): + raise _403_FORBIDDEN_RESPONSE + + class BaseThrottle(BasePermission): """ Rate throttling of requests. -- cgit v1.2.3 From b236241982b95a35cdb251e5020004050fb6567a Mon Sep 17 00:00:00 2001 From: Camille Harang Date: Sat, 11 Feb 2012 01:54:28 +0100 Subject: check authentication after checking ModelResource --- djangorestframework/permissions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'djangorestframework') diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index 100a976e..92e90fc3 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -99,16 +99,16 @@ class DjangoModelPermisson(BasePermission): if self.view.request.method in ('GET', 'OPTIONS', 'HEAD',): return - # User must be logged in to check permissions. - if not hasattr(self.view.request, 'user') or not self.view.request.user.is_authenticated(): - raise _403_FORBIDDEN_RESPONSE - klass = self.view.resource.model # If it doesn't look like a model, we can't check permissions. if not klass or not getattr(klass, '_meta', None): return + # User must be logged in to check permissions. + if not hasattr(self.view.request, 'user') or not self.view.request.user.is_authenticated(): + raise _403_FORBIDDEN_RESPONSE + permission_map = { 'POST': ['%s.add_%s'], 'PUT': ['%s.change_%s'], -- cgit v1.2.3 From 963d2ecccbe30ca231621f85681049983248d08d Mon Sep 17 00:00:00 2001 From: Camille Harang Date: Sat, 11 Feb 2012 02:02:42 +0100 Subject: DjangoModelPermisson's desc --- djangorestframework/permissions.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'djangorestframework') diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index 92e90fc3..cf556dd6 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -91,6 +91,8 @@ class IsUserOrIsAnonReadOnly(BasePermission): class DjangoModelPermisson(BasePermission): """ + The request is authenticated against the Django user's permissions on the + `Resource`'s `Model`, if the resource is a `ModelResource`. """ def check_permission(self, user): -- cgit v1.2.3 From 88561a4ee2762409810b1aa7f85bda923169b69d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 11 Feb 2012 13:00:38 +0000 Subject: Fix up DjangoModelPermissions. --- djangorestframework/permissions.py | 66 ++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 32 deletions(-) (limited to 'djangorestframework') diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index cf556dd6..7c61251d 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -89,45 +89,47 @@ class IsUserOrIsAnonReadOnly(BasePermission): raise _403_FORBIDDEN_RESPONSE -class DjangoModelPermisson(BasePermission): +class DjangoModelPermission(BasePermission): """ The request is authenticated against the Django user's permissions on the - `Resource`'s `Model`, if the resource is a `ModelResource`. - """ - - def check_permission(self, user): - - # GET-style methods are always allowed. - if self.view.request.method in ('GET', 'OPTIONS', 'HEAD',): - return + `Resource`'s `Model`. - klass = self.view.resource.model - - # If it doesn't look like a model, we can't check permissions. - if not klass or not getattr(klass, '_meta', None): - return - - # User must be logged in to check permissions. - if not hasattr(self.view.request, 'user') or not self.view.request.user.is_authenticated(): - raise _403_FORBIDDEN_RESPONSE + This permission should only be used on views with a `ModelResource`. + """ - permission_map = { - 'POST': ['%s.add_%s'], - 'PUT': ['%s.change_%s'], - 'DELETE': ['%s.delete_%s'], - 'PATCH': ['%s.add_%s', '%s.change_%s', '%s.delete_%s'], + # Map methods into required permission codes. + # Override this if you need to also provide 'read' permissions, + # or other custom behaviour. + 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.__name__.lower() } - permission_codes = [] + try: + return [perm % kwargs for perm in self.perms_map[method]] + except KeyError: + ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED) - # If we don't recognize the HTTP method, we don't know what - # permissions to check. Deny. - if self.view.request.method not in permission_map: - raise _403_FORBIDDEN_RESPONSE - - for perm in permission_map[self.view.request.method]: - permission_codes.append(perm % (klass._meta.app_label, klass._meta.module_name)) + 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 self.view.request.user.has_perms(permission_codes): + if not user.has_perms(perms): raise _403_FORBIDDEN_RESPONSE -- cgit v1.2.3 From 2c11fd68f8d57b3675940d4d5bf04f815fe521a6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 11 Feb 2012 17:48:35 +0000 Subject: Minor name change --- djangorestframework/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'djangorestframework') diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index 7c61251d..d47ba3dc 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -89,7 +89,7 @@ class IsUserOrIsAnonReadOnly(BasePermission): raise _403_FORBIDDEN_RESPONSE -class DjangoModelPermission(BasePermission): +class DjangoModelPermissions(BasePermission): """ The request is authenticated against the Django user's permissions on the `Resource`'s `Model`. -- cgit v1.2.3 From cb8d94b956c5a39f15c54f7662bdbd2275ee3e4d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 11 Feb 2012 18:29:24 +0000 Subject: Improve docstring on DjangoModelPermissions, and also ensure the user is authenticated. --- djangorestframework/permissions.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'djangorestframework') diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index d47ba3dc..de24e23b 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -91,15 +91,18 @@ class IsUserOrIsAnonReadOnly(BasePermission): class DjangoModelPermissions(BasePermission): """ - The request is authenticated against the Django user's permissions on the - `Resource`'s `Model`. + The request is authenticated using `django.contrib.auth` permissions. + See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions - This permission should only be used on views with a `ModelResource`. + 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 other custom behaviour. + # or if you want to provide custom permisson codes. perms_map = { 'GET': [], 'OPTIONS': [], @@ -117,7 +120,7 @@ class DjangoModelPermissions(BasePermission): """ kwargs = { 'app_label': model_cls._meta.app_label, - 'model_name': model_cls.__name__.lower() + 'model_name': model_cls._meta.module_name } try: return [perm % kwargs for perm in self.perms_map[method]] @@ -129,7 +132,7 @@ class DjangoModelPermissions(BasePermission): model_cls = self.view.resource.model perms = self.get_required_permissions(method, model_cls) - if not user.has_perms(perms): + if not user.is_authenticated or not user.has_perms(perms): raise _403_FORBIDDEN_RESPONSE -- cgit v1.2.3 From 1ec165f38c508d7ac4c158ec8d558c5d8f1bd15b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 11 Feb 2012 18:43:58 +0000 Subject: `OPTIONS` is also a safe method. --- djangorestframework/permissions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'djangorestframework') diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index de24e23b..2d6d8922 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -20,6 +20,8 @@ __all__ = ( 'PerResourceThrottling' ) +SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] + _403_FORBIDDEN_RESPONSE = ErrorResponse( status.HTTP_403_FORBIDDEN, @@ -84,8 +86,7 @@ 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 -- cgit v1.2.3 From ba1e3b46998254e12578d75428669751e105735b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 11 Feb 2012 21:15:06 +0000 Subject: Fix typo. --- djangorestframework/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'djangorestframework') diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index 2d6d8922..03d78c2e 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -103,7 +103,7 @@ class DjangoModelPermissions(BasePermission): # Map methods into required permission codes. # Override this if you need to also provide 'read' permissions, - # or if you want to provide custom permisson codes. + # or if you want to provide custom permission codes. perms_map = { 'GET': [], 'OPTIONS': [], -- cgit v1.2.3