forked from pool/python-django-braces
* Updates and fixes from #265 * Typo fixes from #269 and #262 * Dropped explicit support for Python versions before 3.7 and non-LTS versions of Django - Dropped patch remove-py2-add-django3.patch: * Included - Added patch modernize-braces.patch: * Add support for modern versions of Django OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-django-braces?expand=0&rev=8
1036 lines
40 KiB
Diff
1036 lines
40 KiB
Diff
From 0031b635b03a4d25c240a9619e4ba5e6433c2697 Mon Sep 17 00:00:00 2001
|
|
From: Kenneth Love <klove@oreilly.com>
|
|
Date: Mon, 8 Nov 2021 16:29:37 -0800
|
|
Subject: [PATCH 01/11] New description
|
|
|
|
---
|
|
braces/__init__.py | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
Index: django-braces-1.15.0/braces/__init__.py
|
|
===================================================================
|
|
--- django-braces-1.15.0.orig/braces/__init__.py
|
|
+++ django-braces-1.15.0/braces/__init__.py
|
|
@@ -2,7 +2,7 @@
|
|
django-braces mixins library
|
|
----------------------------
|
|
|
|
-Several mixins for making Django's generic class-based views more useful.
|
|
+Mixins to make Django's generic class-based views simpler.
|
|
|
|
:copyright: (c) 2013 by Kenneth Love and Chris Jones
|
|
:license: BSD 3-clause. See LICENSE for more details
|
|
Index: django-braces-1.15.0/braces/forms.py
|
|
===================================================================
|
|
--- django-braces-1.15.0.orig/braces/forms.py
|
|
+++ django-braces-1.15.0/braces/forms.py
|
|
@@ -1,4 +1,4 @@
|
|
-class UserKwargModelFormMixin(object):
|
|
+class UserKwargModelFormMixin:
|
|
"""
|
|
Generic model form mixin for popping user out of the kwargs and
|
|
attaching it to the instance.
|
|
@@ -9,6 +9,6 @@ class UserKwargModelFormMixin(object):
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
- self.user = kwargs.pop("user", None) # Pop the user off the
|
|
- # passed in kwargs.
|
|
+ """Remove the user from **kwargs and assign it on the object"""
|
|
+ self.user = kwargs.pop("user", None)
|
|
super(UserKwargModelFormMixin, self).__init__(*args, **kwargs)
|
|
Index: django-braces-1.15.0/braces/views/_access.py
|
|
===================================================================
|
|
--- django-braces-1.15.0.orig/braces/views/_access.py
|
|
+++ django-braces-1.15.0/braces/views/_access.py
|
|
@@ -1,6 +1,6 @@
|
|
import inspect
|
|
import datetime
|
|
-import re
|
|
+import urllib.parse
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth import REDIRECT_FIELD_NAME
|
|
@@ -18,10 +18,9 @@ from django.utils.encoding import force_
|
|
from django.utils.timezone import now
|
|
|
|
|
|
-class AccessMixin(object):
|
|
+class AccessMixin:
|
|
"""
|
|
- 'Abstract' mixin that gives access mixins the same customizable
|
|
- functionality.
|
|
+ Base access mixin. All other access mixins should extend this one.
|
|
"""
|
|
|
|
login_url = None
|
|
@@ -29,6 +28,10 @@ class AccessMixin(object):
|
|
redirect_field_name = REDIRECT_FIELD_NAME # Set by django.contrib.auth
|
|
redirect_unauthenticated_users = False
|
|
|
|
+ def __init__(self, *args, **kwargs):
|
|
+ self._class_name = self.__class__.__name__
|
|
+ super().__init__(*args, **kwargs)
|
|
+
|
|
def get_login_url(self):
|
|
"""
|
|
Override this method to customize the login_url.
|
|
@@ -36,8 +39,8 @@ class AccessMixin(object):
|
|
login_url = self.login_url or settings.LOGIN_URL
|
|
if not login_url:
|
|
raise ImproperlyConfigured(
|
|
- "Define {0}.login_url or settings.LOGIN_URL or override "
|
|
- "{0}.get_login_url().".format(self.__class__.__name__)
|
|
+ f"Define {self._class_name}.login_url or settings.LOGIN_URL or "
|
|
+ f"override {self._class_name}.get_login_url()."
|
|
)
|
|
|
|
return force_str(login_url)
|
|
@@ -48,11 +51,9 @@ class AccessMixin(object):
|
|
"""
|
|
if self.redirect_field_name is None:
|
|
raise ImproperlyConfigured(
|
|
- "{0} is missing the "
|
|
- "redirect_field_name. Define {0}.redirect_field_name or "
|
|
- "override {0}.get_redirect_field_name().".format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f"{self._class_name} is missing the redirect_field_name. "
|
|
+ f"Define {self._class_name}.redirect_field_name or "
|
|
+ f"override {self._class_name}.get_redirect_field_name()."
|
|
)
|
|
return self.redirect_field_name
|
|
|
|
@@ -92,7 +93,7 @@ class AccessMixin(object):
|
|
|
|
class LoginRequiredMixin(AccessMixin):
|
|
"""
|
|
- View mixin which verifies that the user is authenticated.
|
|
+ Requires the user to be authenticated.
|
|
|
|
NOTE:
|
|
This should be the left-most mixin of a view, except when
|
|
@@ -104,21 +105,17 @@ class LoginRequiredMixin(AccessMixin):
|
|
if not request.user.is_authenticated:
|
|
return self.handle_no_permission(request)
|
|
|
|
- return super(LoginRequiredMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
-class AnonymousRequiredMixin(object):
|
|
+class AnonymousRequiredMixin(AccessMixin):
|
|
"""
|
|
- View mixin which redirects to a specified URL if authenticated.
|
|
- Can be useful if you wanted to prevent authenticated users from
|
|
- accessing signup pages etc.
|
|
+ Requires the user to be unauthenticated.
|
|
|
|
NOTE:
|
|
This should be the left-most mixin of a view.
|
|
|
|
- Example Usage
|
|
+ ## Example Usage
|
|
|
|
class SomeView(AnonymousRequiredMixin, ListView):
|
|
...
|
|
@@ -132,36 +129,31 @@ class AnonymousRequiredMixin(object):
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if request.user.is_authenticated:
|
|
return HttpResponseRedirect(self.get_authenticated_redirect_url())
|
|
- return super(AnonymousRequiredMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_authenticated_redirect_url(self):
|
|
"""Return the reversed authenticated redirect url."""
|
|
if not self.authenticated_redirect_url:
|
|
raise ImproperlyConfigured(
|
|
- "{0} is missing an authenticated_redirect_url "
|
|
- "url to redirect to. Define "
|
|
- "{0}.authenticated_redirect_url or override "
|
|
- "{0}.get_authenticated_redirect_url().".format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f"{self._class_name} is missing an authenticated_redirect_url "
|
|
+ f"url to redirect to. Define {self._class_name}.authenticated_redirect_url "
|
|
+ f"or override {self._class_name}.get_authenticated_redirect_url()."
|
|
)
|
|
return resolve_url(self.authenticated_redirect_url)
|
|
|
|
|
|
class PermissionRequiredMixin(AccessMixin):
|
|
"""
|
|
- View mixin which verifies that the logged in user has the specified
|
|
- permission.
|
|
+ The request users must have certain permission(s)
|
|
+
|
|
+ ## Attributes
|
|
|
|
- Class Settings
|
|
`permission_required` - the permission to check for.
|
|
`login_url` - the login url of site
|
|
`redirect_field_name` - defaults to "next"
|
|
`raise_exception` - defaults to False - raise 403 if set to True
|
|
|
|
- Example Usage
|
|
+ ## Example Usage
|
|
|
|
class SomeView(PermissionRequiredMixin, ListView):
|
|
...
|
|
@@ -175,7 +167,7 @@ class PermissionRequiredMixin(AccessMixi
|
|
...
|
|
"""
|
|
|
|
- permission_required = None # Default required perms to none
|
|
+ permission_required = None # No permissions are required by default
|
|
object_level_permissions = False
|
|
|
|
def get_permission_required(self, request=None):
|
|
@@ -188,8 +180,8 @@ class PermissionRequiredMixin(AccessMixi
|
|
# view, or raise a configuration error.
|
|
if self.permission_required is None:
|
|
raise ImproperlyConfigured(
|
|
- '{0} requires the "permission_required" attribute to be '
|
|
- "set.".format(self.__class__.__name__)
|
|
+ f'{self._class_name} requires the "permission_required" '
|
|
+ "attribute to be set."
|
|
)
|
|
|
|
return self.permission_required
|
|
@@ -226,9 +218,7 @@ class PermissionRequiredMixin(AccessMixi
|
|
if not has_permission:
|
|
return self.handle_no_permission(request)
|
|
|
|
- return super(PermissionRequiredMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
class MultiplePermissionsRequiredMixin(PermissionRequiredMixin):
|
|
@@ -242,14 +232,14 @@ class MultiplePermissionsRequiredMixin(P
|
|
permissions in a different format, they should still work.
|
|
|
|
By specifying the `all` key, the user must have all of
|
|
- the permissions in the passed in list.
|
|
+ the permissions in the list.
|
|
|
|
- By specifying The `any` key , the user must have ONE of the set
|
|
+ By specifying the `any` key , the user must have at least one of the
|
|
permissions in the list.
|
|
|
|
Class Settings
|
|
`permissions` - This is required to be a dict with one or both
|
|
- keys of `all` and/or `any` containing a list or tuple of
|
|
+ keys of `all` and `any` containing a list or tuple of
|
|
permissions.
|
|
`login_url` - the login url of site
|
|
`redirect_field_name` - defaults to "next"
|
|
@@ -278,29 +268,25 @@ class MultiplePermissionsRequiredMixin(P
|
|
|
|
def check_permissions(self, request):
|
|
permissions = self.get_permission_required(request)
|
|
- perms_all = permissions.get("all") or None
|
|
- perms_any = permissions.get("any") or None
|
|
+ perms_all = permissions.get("all")
|
|
+ perms_any = permissions.get("any")
|
|
|
|
self._check_permissions_keys_set(perms_all, perms_any)
|
|
self._check_perms_keys("all", perms_all)
|
|
self._check_perms_keys("any", perms_any)
|
|
|
|
- # If perms_all, check that user has all permissions in the list/tuple
|
|
+ # Check that user has all permissions in the list/tuple
|
|
if perms_all:
|
|
+ # Why not `return request.user.has_perms(perms_all)`?
|
|
+ # There may be optional permissions below.
|
|
if not request.user.has_perms(perms_all):
|
|
return False
|
|
|
|
# If perms_any, check that user has at least one in the list/tuple
|
|
if perms_any:
|
|
- has_one_perm = False
|
|
- for perm in perms_any:
|
|
- if request.user.has_perm(perm):
|
|
- has_one_perm = True
|
|
- break
|
|
-
|
|
- if not has_one_perm:
|
|
+ any_perms = [request.user.has_perm(perm) for perm in perms_any]
|
|
+ if not any_perms or not any(any_perms):
|
|
return False
|
|
-
|
|
return True
|
|
|
|
def _check_permissions_attr(self):
|
|
@@ -309,8 +295,8 @@ class MultiplePermissionsRequiredMixin(P
|
|
"""
|
|
if self.permissions is None or not isinstance(self.permissions, dict):
|
|
raise ImproperlyConfigured(
|
|
- '{0} requires the "permissions" attribute to be set as a '
|
|
- "dict.".format(self.__class__.__name__)
|
|
+ f'{self._class_name} requires the `permissions` attribute'
|
|
+ "to be set as a dict."
|
|
)
|
|
|
|
def _check_permissions_keys_set(self, perms_all=None, perms_any=None):
|
|
@@ -321,10 +307,8 @@ class MultiplePermissionsRequiredMixin(P
|
|
"""
|
|
if perms_all is None and perms_any is None:
|
|
raise ImproperlyConfigured(
|
|
- '{0} requires the "permissions" attribute to be set to a '
|
|
- 'dict and the "any" or "all" key to be set.'.format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f'{self._class_name} requires the `permissions` attribute to '
|
|
+ f"be set to a dict and the `any` or `all` key to be set."
|
|
)
|
|
|
|
def _check_perms_keys(self, key=None, perms=None):
|
|
@@ -334,8 +318,8 @@ class MultiplePermissionsRequiredMixin(P
|
|
"""
|
|
if perms and not isinstance(perms, (list, tuple)):
|
|
raise ImproperlyConfigured(
|
|
- "{0} requires the permisions dict {1} value to be a "
|
|
- "list or tuple.".format(self.__class__.__name__, key)
|
|
+ f"{self._class_name} requires the permissions dict {key} value "
|
|
+ "to be a list or tuple."
|
|
)
|
|
|
|
|
|
@@ -343,23 +327,25 @@ class GroupRequiredMixin(AccessMixin):
|
|
group_required = None
|
|
|
|
def get_group_required(self):
|
|
- if self.group_required is None or (
|
|
+ if any([
|
|
+ self.group_required is None,
|
|
not isinstance(self.group_required, (list, tuple, str))
|
|
- ):
|
|
+ ]):
|
|
|
|
raise ImproperlyConfigured(
|
|
- '{0} requires the "group_required" attribute to be set and be '
|
|
- "one of the following types: string, unicode, list or "
|
|
- "tuple".format(self.__class__.__name__)
|
|
+ f'{self._class_name} requires the `group_required` attribute '
|
|
+ "to be set and be a string, list, or tuple."
|
|
)
|
|
if not isinstance(self.group_required, (list, tuple)):
|
|
self.group_required = (self.group_required,)
|
|
return self.group_required
|
|
|
|
def check_membership(self, groups):
|
|
- """Check required group(s)"""
|
|
+ """Check for user's membership in required groups. Superusers are
|
|
+ automatically members"""
|
|
if self.request.user.is_superuser:
|
|
return True
|
|
+
|
|
user_groups = self.request.user.groups.values_list("name", flat=True)
|
|
return set(groups).intersection(set(user_groups))
|
|
|
|
@@ -372,15 +358,12 @@ class GroupRequiredMixin(AccessMixin):
|
|
if not in_group:
|
|
return self.handle_no_permission(request)
|
|
|
|
- return super(GroupRequiredMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
class UserPassesTestMixin(AccessMixin):
|
|
"""
|
|
- CBV Mixin allows you to define test that every user should pass
|
|
- to get access into view.
|
|
+ User must pass a test before being allowed access to the view.
|
|
|
|
Class Settings
|
|
`test_func` - This is required to be a method that takes user
|
|
@@ -392,10 +375,8 @@ class UserPassesTestMixin(AccessMixin):
|
|
|
|
def test_func(self, user):
|
|
raise NotImplementedError(
|
|
- "{0} is missing implementation of the "
|
|
- "test_func method. You should write one.".format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f"{self._class_name} is missing implementation of the "
|
|
+ "`test_func` method. A function to test the user is required."
|
|
)
|
|
|
|
def get_test_func(self):
|
|
@@ -407,52 +388,44 @@ class UserPassesTestMixin(AccessMixin):
|
|
if not user_test_result:
|
|
return self.handle_no_permission(request)
|
|
|
|
- return super(UserPassesTestMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
class SuperuserRequiredMixin(AccessMixin):
|
|
"""
|
|
- Mixin allows you to require a user with `is_superuser` set to True.
|
|
+ Require users to be superusers to access the view.
|
|
"""
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_superuser:
|
|
return self.handle_no_permission(request)
|
|
|
|
- return super(SuperuserRequiredMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
class StaffuserRequiredMixin(AccessMixin):
|
|
"""
|
|
- Mixin allows you to require a user with `is_staff` set to True.
|
|
+ Require users to be marked as staff to access the view.
|
|
"""
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
return self.handle_no_permission(request)
|
|
|
|
- return super(StaffuserRequiredMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
-class SSLRequiredMixin(object):
|
|
+class SSLRequiredMixin:
|
|
"""
|
|
- Simple mixin that allows you to force a view to be accessed
|
|
- via https.
|
|
+ Require requests to be made over a secure connection.
|
|
"""
|
|
|
|
- raise_exception = False # Default whether to raise an exception to none
|
|
+ raise_exception = False
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if getattr(settings, "DEBUG", False):
|
|
- return super(SSLRequiredMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ # Don't enforce the check during development
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
if not request.is_secure():
|
|
if self.raise_exception:
|
|
@@ -467,25 +440,23 @@ class SSLRequiredMixin(object):
|
|
def _build_https_url(self, request):
|
|
"""Get the full url, replace http with https"""
|
|
url = request.build_absolute_uri(request.get_full_path())
|
|
- return re.sub(r"^http", "https", url)
|
|
+ return urllib.parse.urlunsplit(
|
|
+ ("https",)+urllib.parse.urlsplit(url)[1:]
|
|
+ )
|
|
|
|
|
|
class RecentLoginRequiredMixin(LoginRequiredMixin):
|
|
"""
|
|
- Mixin allows you to require a login to be within a number of seconds.
|
|
+ Require the user to have logged in within a number of seconds.
|
|
"""
|
|
|
|
max_last_login_delta = 1800 # Defaults to 30 minutes
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
- resp = super(RecentLoginRequiredMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ resp = super().dispatch(request, *args, **kwargs)
|
|
|
|
if resp.status_code == 200:
|
|
delta = datetime.timedelta(seconds=self.max_last_login_delta)
|
|
if now() > (request.user.last_login + delta):
|
|
return logout_then_login(request, self.get_login_url())
|
|
- else:
|
|
- return resp
|
|
return resp
|
|
Index: django-braces-1.15.0/braces/views/_ajax.py
|
|
===================================================================
|
|
--- django-braces-1.15.0.orig/braces/views/_ajax.py
|
|
+++ django-braces-1.15.0/braces/views/_ajax.py
|
|
@@ -5,10 +5,12 @@ from django.core.serializers.json import
|
|
from django.http import HttpResponse, HttpResponseBadRequest
|
|
|
|
|
|
-class JSONResponseMixin(object):
|
|
+class JSONResponseMixin:
|
|
"""
|
|
- A mixin that allows you to easily serialize simple data such as a dict or
|
|
- Django models.
|
|
+ Basic serialized responses.
|
|
+
|
|
+ For anything more complicated than basic Python types or Django
|
|
+ models, please use something like django-rest-framework.
|
|
"""
|
|
|
|
content_type = None
|
|
@@ -19,24 +21,22 @@ class JSONResponseMixin(object):
|
|
if self.content_type is not None and not isinstance(
|
|
self.content_type, str
|
|
):
|
|
+ class_name = self.__class__.__name__
|
|
raise ImproperlyConfigured(
|
|
- "{0} is missing a content type. Define {0}.content_type, "
|
|
- "or override {0}.get_content_type().".format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f"{class_name} is missing a content type. Define {class_name}"
|
|
+ ".content_type or override {class_name}.get_content_type()."
|
|
)
|
|
return self.content_type or "application/json"
|
|
|
|
def get_json_dumps_kwargs(self):
|
|
- if self.json_dumps_kwargs is None:
|
|
- self.json_dumps_kwargs = {}
|
|
- self.json_dumps_kwargs.setdefault("ensure_ascii", False)
|
|
- return self.json_dumps_kwargs
|
|
+ dumps_kwargs = getattr(self, "json_dumps_kwargs", None) or {}
|
|
+ dumps_kwargs.setdefault("ensure_ascii", False)
|
|
+ return dumps_kwargs
|
|
|
|
def render_json_response(self, context_dict, status=200):
|
|
"""
|
|
- Limited serialization for shipping plain data. Do not use for models
|
|
- or other complex or custom objects.
|
|
+ Limited serialization for shipping plain data.
|
|
+ Do not use for models or other complex objects.
|
|
"""
|
|
json_context = json.dumps(
|
|
context_dict,
|
|
@@ -56,7 +56,7 @@ class JSONResponseMixin(object):
|
|
return HttpResponse(json_data, content_type=self.get_content_type())
|
|
|
|
|
|
-class AjaxResponseMixin(object):
|
|
+class AjaxResponseMixin:
|
|
"""
|
|
Mixin allows you to define alternative methods for ajax requests. Similar
|
|
to the normal get, post, and put methods, you can use get_ajax, post_ajax,
|
|
@@ -64,12 +64,13 @@ class AjaxResponseMixin(object):
|
|
"""
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
- request_method = request.method.lower()
|
|
-
|
|
- if request.is_ajax() and request_method in self.http_method_names:
|
|
+ if all([
|
|
+ request.headers.get("x-requested-with") == "XMLHttpRequest",
|
|
+ request.method.lower() in self.http_method_names
|
|
+ ]):
|
|
handler = getattr(
|
|
self,
|
|
- "{0}_ajax".format(request_method),
|
|
+ f"{request.method.lower()}_ajax",
|
|
self.http_method_not_allowed,
|
|
)
|
|
self.request = request
|
|
@@ -77,9 +78,7 @@ class AjaxResponseMixin(object):
|
|
self.kwargs = kwargs
|
|
return handler(request, *args, **kwargs)
|
|
|
|
- return super(AjaxResponseMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_ajax(self, request, *args, **kwargs):
|
|
return self.get(request, *args, **kwargs)
|
|
@@ -96,9 +95,11 @@ class AjaxResponseMixin(object):
|
|
|
|
class JsonRequestResponseMixin(JSONResponseMixin):
|
|
"""
|
|
- Extends JSONResponseMixin. Attempts to parse request as JSON. If request
|
|
- is properly formatted, the json is saved to self.request_json as a Python
|
|
- object. request_json will be None for imparsible requests.
|
|
+ Attempt to parse the request body as JSON.
|
|
+
|
|
+ If successful, self.request_json will contain the deserialized object.
|
|
+ Otherwise, self.request_json will be None.
|
|
+
|
|
Set the attribute require_json to True to return a 400 "Bad Request" error
|
|
for requests that don't contain JSON.
|
|
|
|
@@ -132,7 +133,7 @@ class JsonRequestResponseMixin(JSONRespo
|
|
def get_request_json(self):
|
|
try:
|
|
return json.loads(self.request.body.decode("utf-8"))
|
|
- except ValueError:
|
|
+ except (json.JSONDecodeError, ValueError):
|
|
return None
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
@@ -149,9 +150,7 @@ class JsonRequestResponseMixin(JSONRespo
|
|
]
|
|
):
|
|
return self.render_bad_request_response()
|
|
- return super(JsonRequestResponseMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
class JSONRequestResponseMixin(JsonRequestResponseMixin):
|
|
Index: django-braces-1.15.0/braces/views/_forms.py
|
|
===================================================================
|
|
--- django-braces-1.15.0.orig/braces/views/_forms.py
|
|
+++ django-braces-1.15.0/braces/views/_forms.py
|
|
@@ -9,7 +9,7 @@ from django.views.decorators.csrf import
|
|
from django.urls import reverse
|
|
|
|
|
|
-class CsrfExemptMixin(object):
|
|
+class CsrfExemptMixin:
|
|
"""
|
|
Exempts the view from CSRF requirements.
|
|
|
|
@@ -19,31 +19,32 @@ class CsrfExemptMixin(object):
|
|
|
|
@method_decorator(csrf_exempt)
|
|
def dispatch(self, *args, **kwargs):
|
|
- return super(CsrfExemptMixin, self).dispatch(*args, **kwargs)
|
|
+ return super().dispatch(*args, **kwargs)
|
|
|
|
|
|
-class UserFormKwargsMixin(object):
|
|
+class UserFormKwargsMixin:
|
|
"""
|
|
- CBV mixin which puts the user from the request into the form kwargs.
|
|
- Note: Using this mixin requires you to pop the `user` kwarg
|
|
- out of the dict in the super of your form's `__init__`.
|
|
+ Automatically include `request.user` in form kwargs.
|
|
+
|
|
+ ## Note
|
|
+ You will need to handle the `user` kwarg in your form. Usually
|
|
+ this means `user = kwargs.pop("user")` in your form's `__init__`.
|
|
"""
|
|
|
|
def get_form_kwargs(self):
|
|
- kwargs = super(UserFormKwargsMixin, self).get_form_kwargs()
|
|
+ kwargs = super().get_form_kwargs()
|
|
# Update the existing form kwargs dict with the request's user.
|
|
kwargs.update({"user": self.request.user})
|
|
return kwargs
|
|
|
|
|
|
-class SuccessURLRedirectListMixin(object):
|
|
+class SuccessURLRedirectListMixin:
|
|
"""
|
|
- Simple CBV mixin which sets the success url to the list view of
|
|
- a given app. Set success_list_url as a class attribute of your
|
|
- CBV and don't worry about overloading the get_success_url.
|
|
+ Automatically reverses `success_list_url` and returns that as
|
|
+ the `success_url` for a form view.
|
|
|
|
- This is only to be used for redirecting to a list page. If you need
|
|
- to reverse the url with kwargs, this is not the mixin to use.
|
|
+ This is meant to redirect to a view without arguments. If you need
|
|
+ to include arguments to `reverse`, you can omit this mixin.
|
|
"""
|
|
|
|
success_list_url = None # Default the success url to none
|
|
@@ -51,19 +52,20 @@ class SuccessURLRedirectListMixin(object
|
|
def get_success_url(self):
|
|
# Return the reversed success url.
|
|
if self.success_list_url is None:
|
|
+ class_name = self.__class__.__name__
|
|
raise ImproperlyConfigured(
|
|
- "{0} is missing a success_list_url "
|
|
- "name to reverse and redirect to. Define "
|
|
- "{0}.success_list_url or override "
|
|
- "{0}.get_success_url().".format(self.__class__.__name__)
|
|
+ f"{class_name} is missing a success_list_url attribute. "
|
|
+ f"Define {class_name}.success_list_url or override "
|
|
+ f"{class_name}.get_success_url()."
|
|
)
|
|
return reverse(self.success_list_url)
|
|
|
|
|
|
-class _MessageAPIWrapper(object):
|
|
+class _MessageAPIWrapper:
|
|
"""
|
|
- Wrap the django.contrib.messages.api module to automatically pass a given
|
|
- request object as the first parameter of function calls.
|
|
+ Wrapper for the django.contrib.messages.api module.
|
|
+ Automatically pass a request object as the first parameter of
|
|
+ message function calls.
|
|
"""
|
|
|
|
API = set(
|
|
@@ -86,59 +88,59 @@ class _MessageAPIWrapper(object):
|
|
setattr(self, name, partial(api_fn, request))
|
|
|
|
|
|
-class _MessageDescriptor(object):
|
|
+class _MessageDescriptor:
|
|
"""
|
|
A descriptor that binds the _MessageAPIWrapper to the view's
|
|
request.
|
|
"""
|
|
|
|
- def __get__(self, instance, owner):
|
|
+ def __get__(self, instance, *args, **kwargs):
|
|
return _MessageAPIWrapper(instance.request)
|
|
|
|
|
|
-class MessageMixin(object):
|
|
+class MessageMixin:
|
|
"""
|
|
Add a `messages` attribute on the view instance that wraps
|
|
- `django.contrib .messages`, automatically passing the current
|
|
+ `django.contrib.messages`, automatically passing the current
|
|
request object.
|
|
"""
|
|
|
|
messages = _MessageDescriptor()
|
|
|
|
+ def __init__(self, *args, **kwargs):
|
|
+ super().__init__(*args, **kwargs)
|
|
+ self._class_name = self.__class__.__name__
|
|
+
|
|
|
|
class FormValidMessageMixin(MessageMixin):
|
|
"""
|
|
- Mixin allows you to set static message which is displayed by
|
|
- Django's messages framework through a static property on the class
|
|
- or programmatically by overloading the get_form_valid_message method.
|
|
+ Set a string to be sent via Django's messages framework when a form
|
|
+ passes validation.
|
|
"""
|
|
|
|
form_valid_message = None # Default to None
|
|
|
|
def get_form_valid_message(self):
|
|
"""
|
|
- Validate that form_valid_message is set and is either a
|
|
- unicode or str object.
|
|
+ Validate that form_valid_message is set correctly
|
|
"""
|
|
if self.form_valid_message is None:
|
|
raise ImproperlyConfigured(
|
|
- "{0}.form_valid_message is not set. Define "
|
|
- "{0}.form_valid_message, or override "
|
|
- "{0}.get_form_valid_message().".format(self.__class__.__name__)
|
|
+ f"{self._class_name}.form_valid_message is not set. Define "
|
|
+ f"{self._class_name}.form_valid_message, or override "
|
|
+ f"{self._class_name}.get_form_valid_message()."
|
|
)
|
|
|
|
if not isinstance(self.form_valid_message, (str, Promise)):
|
|
raise ImproperlyConfigured(
|
|
- "{0}.form_valid_message must be a str or unicode "
|
|
- "object.".format(self.__class__.__name__)
|
|
+ f"{self._class_name}.form_valid_message must be a str or Promise."
|
|
)
|
|
|
|
return force_str(self.form_valid_message)
|
|
|
|
def form_valid(self, form):
|
|
"""
|
|
- Call the super first, so that when overriding
|
|
- get_form_valid_message, we have access to the newly saved object.
|
|
+ Set the "form valid" message for standard form validation
|
|
"""
|
|
response = super(FormValidMessageMixin, self).form_valid(form)
|
|
self.messages.success(
|
|
@@ -147,6 +149,9 @@ class FormValidMessageMixin(MessageMixin
|
|
return response
|
|
|
|
def delete(self, *args, **kwargs):
|
|
+ """
|
|
+ Set the "form valid" message for delete form validation
|
|
+ """
|
|
response = super(FormValidMessageMixin, self).delete(*args, **kwargs)
|
|
self.messages.success(
|
|
self.get_form_valid_message(), fail_silently=True
|
|
@@ -156,36 +161,34 @@ class FormValidMessageMixin(MessageMixin
|
|
|
|
class FormInvalidMessageMixin(MessageMixin):
|
|
"""
|
|
- Mixin allows you to set static message which is displayed by
|
|
- Django's messages framework through a static property on the class
|
|
- or programmatically by overloading the get_form_invalid_message method.
|
|
+ Set a string to be sent via Django's messages framework when a form
|
|
+ fails validation.
|
|
"""
|
|
|
|
form_invalid_message = None
|
|
|
|
def get_form_invalid_message(self):
|
|
"""
|
|
- Validate that form_invalid_message is set and is either a
|
|
- unicode or str object.
|
|
+ Validate that form_invalid_message is set correctly.
|
|
"""
|
|
if self.form_invalid_message is None:
|
|
raise ImproperlyConfigured(
|
|
- "{0}.form_invalid_message is not set. Define "
|
|
- "{0}.form_invalid_message, or override "
|
|
- "{0}.get_form_invalid_message().".format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f"{self._class_name}.form_invalid_message is not set. Define "
|
|
+ f"{self._class_name}.form_invalid_message, or override "
|
|
+ f"{self._class_name}.get_form_invalid_message()."
|
|
)
|
|
|
|
if not isinstance(self.form_invalid_message, (str, Promise)):
|
|
raise ImproperlyConfigured(
|
|
- "{0}.form_invalid_message must be a str or unicode "
|
|
- "object.".format(self.__class__.__name__)
|
|
+ f"{self._class_name}.form_invalid_message must be a str or Promise."
|
|
)
|
|
|
|
return force_str(self.form_invalid_message)
|
|
|
|
def form_invalid(self, form):
|
|
+ """
|
|
+ Set the "form invalid" message for standard form validation
|
|
+ """
|
|
response = super(FormInvalidMessageMixin, self).form_invalid(form)
|
|
self.messages.error(
|
|
self.get_form_invalid_message(), fail_silently=True
|
|
@@ -195,8 +198,5 @@ class FormInvalidMessageMixin(MessageMix
|
|
|
|
class FormMessagesMixin(FormValidMessageMixin, FormInvalidMessageMixin):
|
|
"""
|
|
- Mixin is a shortcut to use both FormValidMessageMixin and
|
|
- FormInvalidMessageMixin.
|
|
+ Set messages to be sent whether a form is valid or invalid.
|
|
"""
|
|
-
|
|
- pass
|
|
Index: django-braces-1.15.0/braces/views/_other.py
|
|
===================================================================
|
|
--- django-braces-1.15.0.orig/braces/views/_other.py
|
|
+++ django-braces-1.15.0/braces/views/_other.py
|
|
@@ -101,9 +101,7 @@ class CanonicalSlugDetailMixin(object):
|
|
}
|
|
return redirect(current_urlpattern, **params)
|
|
|
|
- return super(CanonicalSlugDetailMixin, self).dispatch(
|
|
- request, *args, **kwargs
|
|
- )
|
|
+ return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_canonical_slug(self):
|
|
"""
|
|
@@ -116,11 +114,11 @@ class CanonicalSlugDetailMixin(object):
|
|
return self.get_object().slug
|
|
|
|
|
|
-class AllVerbsMixin(object):
|
|
+class AllVerbsMixin:
|
|
"""Call a single method for all HTTP verbs.
|
|
|
|
The name of the method should be specified using the class attribute
|
|
- ``all_handler``. The default value of this attribute is 'all'.
|
|
+ `all_handler`. The default value of this attribute is 'all'.
|
|
"""
|
|
|
|
all_handler = "all"
|
|
@@ -128,18 +126,16 @@ class AllVerbsMixin(object):
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not self.all_handler:
|
|
raise ImproperlyConfigured(
|
|
- "{0} requires the all_handler attribute to be set.".format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f"{self.__class__.__name__} requires the all_handler attribute to be set."
|
|
)
|
|
|
|
handler = getattr(self, self.all_handler, self.http_method_not_allowed)
|
|
return handler(request, *args, **kwargs)
|
|
|
|
|
|
-class HeaderMixin(object):
|
|
+class HeaderMixin:
|
|
"""
|
|
- Add arbitrary HTTP headers to a response by specifying them in the
|
|
+ Add extra HTTP headers to a response by specifying them in the
|
|
``headers`` attribute or by overriding the ``get_headers()`` method.
|
|
"""
|
|
|
|
Index: django-braces-1.15.0/braces/views/_queries.py
|
|
===================================================================
|
|
--- django-braces-1.15.0.orig/braces/views/_queries.py
|
|
+++ django-braces-1.15.0/braces/views/_queries.py
|
|
@@ -3,10 +3,9 @@ import warnings
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
|
|
|
|
-class SelectRelatedMixin(object):
|
|
+class SelectRelatedMixin:
|
|
"""
|
|
- Mixin allows you to provide a tuple or list of related models to
|
|
- perform a select_related on.
|
|
+ Automatically apply `select_related` for a list of relations.
|
|
"""
|
|
|
|
select_related = None # Default related fields to none
|
|
@@ -15,20 +14,19 @@ class SelectRelatedMixin(object):
|
|
if self.select_related is None:
|
|
# If no fields were provided, raise a configuration error
|
|
raise ImproperlyConfigured(
|
|
- "{0} is missing the select_related property. This must be "
|
|
- "a tuple or list.".format(self.__class__.__name__)
|
|
+ f"{self.__class__.__name__} is missing the select_related attribute."
|
|
)
|
|
|
|
if not isinstance(self.select_related, (tuple, list)):
|
|
# If the select_related argument is *not* a tuple or list,
|
|
# raise a configuration error.
|
|
raise ImproperlyConfigured(
|
|
- "{0}'s select_related property must be a tuple or "
|
|
- "list.".format(self.__class__.__name__)
|
|
+ f"{self.__class__.__name__}'s select_related property must be "
|
|
+ "a tuple or list."
|
|
)
|
|
|
|
# Get the current queryset of the view
|
|
- queryset = super(SelectRelatedMixin, self).get_queryset()
|
|
+ queryset = super().get_queryset()
|
|
|
|
if not self.select_related:
|
|
warnings.warn("The select_related attribute is empty")
|
|
@@ -37,10 +35,9 @@ class SelectRelatedMixin(object):
|
|
return queryset.select_related(*self.select_related)
|
|
|
|
|
|
-class PrefetchRelatedMixin(object):
|
|
+class PrefetchRelatedMixin:
|
|
"""
|
|
- Mixin allows you to provide a tuple or list of related models to
|
|
- perform a prefetch_related on.
|
|
+ Automatically apply `prefetch_related` for a list of relations.
|
|
"""
|
|
|
|
prefetch_related = None # Default prefetch fields to none
|
|
@@ -49,20 +46,18 @@ class PrefetchRelatedMixin(object):
|
|
if self.prefetch_related is None:
|
|
# If no fields were provided, raise a configuration error
|
|
raise ImproperlyConfigured(
|
|
- "{0} is missing the prefetch_related property. This must be "
|
|
- "a tuple or list.".format(self.__class__.__name__)
|
|
+ f"{self.__class__.__name__} is missing the prefetch_related attribute."
|
|
)
|
|
|
|
if not isinstance(self.prefetch_related, (tuple, list)):
|
|
# If the prefetch_related argument is *not* a tuple or list,
|
|
# raise a configuration error.
|
|
raise ImproperlyConfigured(
|
|
- "{0}'s prefetch_related property must be a tuple or "
|
|
- "list.".format(self.__class__.__name__)
|
|
+ f"{self.__class__.__name__}'s prefetch_related property must be a tuple or list."
|
|
)
|
|
|
|
# Get the current queryset of the view
|
|
- queryset = super(PrefetchRelatedMixin, self).get_queryset()
|
|
+ queryset = super().get_queryset()
|
|
|
|
if not self.prefetch_related:
|
|
warnings.warn("The prefetch_related attribute is empty")
|
|
@@ -71,9 +66,9 @@ class PrefetchRelatedMixin(object):
|
|
return queryset.prefetch_related(*self.prefetch_related)
|
|
|
|
|
|
-class OrderableListMixin(object):
|
|
+class OrderableListMixin:
|
|
"""
|
|
- Mixin allows your users to order records using GET parameters
|
|
+ Order the queryset based on GET parameters.
|
|
"""
|
|
|
|
orderable_columns = None
|
|
@@ -89,7 +84,7 @@ class OrderableListMixin(object):
|
|
* ``order_by`` - name of the field
|
|
* ``ordering`` - order of ordering, either ``asc`` or ``desc``
|
|
"""
|
|
- context = super(OrderableListMixin, self).get_context_data(**kwargs)
|
|
+ context = super().get_context_data(**kwargs)
|
|
context["order_by"] = self.order_by
|
|
context["ordering"] = self.ordering
|
|
return context
|
|
@@ -97,18 +92,14 @@ class OrderableListMixin(object):
|
|
def get_orderable_columns(self):
|
|
if not self.orderable_columns:
|
|
raise ImproperlyConfigured(
|
|
- "{0} needs the ordering columns defined.".format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f"{self.__class__.__name__} needs the ordering columns defined."
|
|
)
|
|
return self.orderable_columns
|
|
|
|
def get_orderable_columns_default(self):
|
|
if not self.orderable_columns_default:
|
|
raise ImproperlyConfigured(
|
|
- "{0} needs the default ordering column defined.".format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f"{self.__class__.__name__} needs the default ordering column defined."
|
|
)
|
|
return self.orderable_columns_default
|
|
|
|
@@ -118,9 +109,7 @@ class OrderableListMixin(object):
|
|
else:
|
|
if self.ordering_default not in ["asc", "desc"]:
|
|
raise ImproperlyConfigured(
|
|
- "{0} only allows asc or desc as ordering option".format(
|
|
- self.__class__.__name__
|
|
- )
|
|
+ f"{self.__class__.__name__} only allows asc or desc as ordering option"
|
|
)
|
|
return self.ordering_default
|
|
|
|
@@ -141,11 +130,10 @@ class OrderableListMixin(object):
|
|
self.order_by = order_by
|
|
self.ordering = self.get_ordering_default()
|
|
|
|
- if (
|
|
- order_by
|
|
- and self.request.GET.get("ordering", self.ordering) == "desc"
|
|
- ):
|
|
- order_by = "-" + order_by
|
|
+ if all([order_by,
|
|
+ self.request.GET.get("ordering", self.ordering) == "desc"
|
|
+ ]):
|
|
+ order_by = f"-{order_by}"
|
|
self.ordering = self.request.GET.get("ordering", self.ordering)
|
|
|
|
return queryset.order_by(order_by)
|
|
@@ -154,5 +142,5 @@ class OrderableListMixin(object):
|
|
"""
|
|
Returns ordered ``QuerySet``
|
|
"""
|
|
- unordered_queryset = super(OrderableListMixin, self).get_queryset()
|
|
+ unordered_queryset = super().get_queryset()
|
|
return self.get_ordered_queryset(unordered_queryset)
|
|
Index: django-braces-1.15.0/tests/test_other_mixins.py
|
|
===================================================================
|
|
--- django-braces-1.15.0.orig/tests/test_other_mixins.py
|
|
+++ django-braces-1.15.0/tests/test_other_mixins.py
|
|
@@ -562,7 +562,7 @@ class TestModelCanonicalSlugDetailView(t
|
|
class MessageMixinTests(test.TestCase):
|
|
def setUp(self):
|
|
self.rf = test.RequestFactory()
|
|
- self.middleware = MessageMiddleware()
|
|
+ self.middleware = MessageMiddleware("")
|
|
|
|
def get_request(self, *args, **kwargs):
|
|
request = self.rf.get("/")
|