component_framework.adapters.django_permissions
Django FBV decorators for component access control.
These decorators always return JSON responses (401/403), never redirects, making them safe for HTMX/fetch consumers.
1"""Django FBV decorators for component access control. 2 3These decorators always return JSON responses (401/403), never redirects, 4making them safe for HTMX/fetch consumers. 5""" 6 7import functools 8 9try: 10 from django.http import HttpRequest, JsonResponse 11except ImportError as e: 12 from . import _require_extra 13 14 raise _require_extra("django", "django") from e 15 16 17def login_required_component(view_func): 18 """ 19 Decorator that requires the user to be authenticated. 20 21 Returns a JSON 401 response (never a redirect) for unauthenticated requests. 22 23 Usage:: 24 25 from component_framework.adapters.django_permissions import login_required_component 26 27 urlpatterns = [ 28 path("components/<str:name>/", login_required_component(component_view)), 29 ] 30 """ 31 32 @functools.wraps(view_func) 33 def wrapper(request: HttpRequest, *args, **kwargs): 34 user = getattr(request, "user", None) 35 if not user or not user.is_authenticated: 36 return JsonResponse({"error": "Authentication required"}, status=401) 37 return view_func(request, *args, **kwargs) 38 39 return wrapper 40 41 42def permission_required_component(perm: str | list[str]): 43 """ 44 Decorator factory that requires the user to hold specific Django permissions. 45 46 Returns a JSON 403 response (never a redirect) if permission is denied. 47 48 Args: 49 perm: A permission string (e.g. ``"myapp.change_model"``) or list of 50 permission strings. When a list is given, the user must hold *all* 51 permissions. 52 53 Usage:: 54 55 from component_framework.adapters.django_permissions import permission_required_component 56 57 urlpatterns = [ 58 path( 59 "components/<str:name>/", 60 permission_required_component("myapp.change_model")(component_view), 61 ), 62 ] 63 """ 64 perms: list[str] = [perm] if isinstance(perm, str) else list(perm) 65 66 def decorator(view_func): 67 @functools.wraps(view_func) 68 def wrapper(request: HttpRequest, *args, **kwargs): 69 user = getattr(request, "user", None) 70 if not user or not user.is_authenticated: 71 return JsonResponse({"error": "Authentication required"}, status=401) 72 if not all(user.has_perm(p) for p in perms): 73 return JsonResponse({"error": "Permission denied"}, status=403) 74 return view_func(request, *args, **kwargs) 75 76 return wrapper 77 78 return decorator 79 80 81def staff_required_component(view_func): 82 """ 83 Decorator that requires the user to be a staff member. 84 85 Returns a JSON 403 response (never a redirect) for non-staff requests. 86 87 Usage:: 88 89 from component_framework.adapters.django_permissions import staff_required_component 90 91 urlpatterns = [ 92 path("components/<str:name>/", staff_required_component(component_view)), 93 ] 94 """ 95 96 @functools.wraps(view_func) 97 def wrapper(request: HttpRequest, *args, **kwargs): 98 user = getattr(request, "user", None) 99 if not user or not user.is_authenticated: 100 return JsonResponse({"error": "Authentication required"}, status=401) 101 if not user.is_staff: 102 return JsonResponse({"error": "Staff access required"}, status=403) 103 return view_func(request, *args, **kwargs) 104 105 return wrapper
def
login_required_component(view_func):
18def login_required_component(view_func): 19 """ 20 Decorator that requires the user to be authenticated. 21 22 Returns a JSON 401 response (never a redirect) for unauthenticated requests. 23 24 Usage:: 25 26 from component_framework.adapters.django_permissions import login_required_component 27 28 urlpatterns = [ 29 path("components/<str:name>/", login_required_component(component_view)), 30 ] 31 """ 32 33 @functools.wraps(view_func) 34 def wrapper(request: HttpRequest, *args, **kwargs): 35 user = getattr(request, "user", None) 36 if not user or not user.is_authenticated: 37 return JsonResponse({"error": "Authentication required"}, status=401) 38 return view_func(request, *args, **kwargs) 39 40 return wrapper
Decorator that requires the user to be authenticated.
Returns a JSON 401 response (never a redirect) for unauthenticated requests.
Usage::
from component_framework.adapters.django_permissions import login_required_component
urlpatterns = [
path("components/<str:name>/", login_required_component(component_view)),
]
def
permission_required_component(perm: str | list[str]):
43def permission_required_component(perm: str | list[str]): 44 """ 45 Decorator factory that requires the user to hold specific Django permissions. 46 47 Returns a JSON 403 response (never a redirect) if permission is denied. 48 49 Args: 50 perm: A permission string (e.g. ``"myapp.change_model"``) or list of 51 permission strings. When a list is given, the user must hold *all* 52 permissions. 53 54 Usage:: 55 56 from component_framework.adapters.django_permissions import permission_required_component 57 58 urlpatterns = [ 59 path( 60 "components/<str:name>/", 61 permission_required_component("myapp.change_model")(component_view), 62 ), 63 ] 64 """ 65 perms: list[str] = [perm] if isinstance(perm, str) else list(perm) 66 67 def decorator(view_func): 68 @functools.wraps(view_func) 69 def wrapper(request: HttpRequest, *args, **kwargs): 70 user = getattr(request, "user", None) 71 if not user or not user.is_authenticated: 72 return JsonResponse({"error": "Authentication required"}, status=401) 73 if not all(user.has_perm(p) for p in perms): 74 return JsonResponse({"error": "Permission denied"}, status=403) 75 return view_func(request, *args, **kwargs) 76 77 return wrapper 78 79 return decorator
Decorator factory that requires the user to hold specific Django permissions.
Returns a JSON 403 response (never a redirect) if permission is denied.
Arguments:
- perm: A permission string (e.g.
"myapp.change_model") or list of permission strings. When a list is given, the user must hold all permissions.
Usage::
from component_framework.adapters.django_permissions import permission_required_component
urlpatterns = [
path(
"components/<str:name>/",
permission_required_component("myapp.change_model")(component_view),
),
]
def
staff_required_component(view_func):
82def staff_required_component(view_func): 83 """ 84 Decorator that requires the user to be a staff member. 85 86 Returns a JSON 403 response (never a redirect) for non-staff requests. 87 88 Usage:: 89 90 from component_framework.adapters.django_permissions import staff_required_component 91 92 urlpatterns = [ 93 path("components/<str:name>/", staff_required_component(component_view)), 94 ] 95 """ 96 97 @functools.wraps(view_func) 98 def wrapper(request: HttpRequest, *args, **kwargs): 99 user = getattr(request, "user", None) 100 if not user or not user.is_authenticated: 101 return JsonResponse({"error": "Authentication required"}, status=401) 102 if not user.is_staff: 103 return JsonResponse({"error": "Staff access required"}, status=403) 104 return view_func(request, *args, **kwargs) 105 106 return wrapper
Decorator that requires the user to be a staff member.
Returns a JSON 403 response (never a redirect) for non-staff requests.
Usage::
from component_framework.adapters.django_permissions import staff_required_component
urlpatterns = [
path("components/<str:name>/", staff_required_component(component_view)),
]