from __future__ import annotations from functools import wraps from typing import Iterable, Callable, Optional from django.contrib import messages from django.contrib.auth.views import redirect_to_login from django.shortcuts import redirect from django.urls import reverse def permissions_required(*perms: str, any_perm: bool = False, message: Optional[str] = None, login_view: str = "admin_frontend:login"): """Decorator to enforce one or more Django permissions on a view. - If the user is not authenticated, redirects to the admin login, preserving next. - If permissions fail, adds an error message and redirects back to the current URL. Usage: @permissions_required('recycle_core.assign_driver') def my_view(...): ... @permissions_required('app.perm_a', 'app.perm_b') # ALL required def another(...): ... @permissions_required('app.perm_a', 'app.perm_b', any_perm=True) # ANY required def another(...): ... """ def decorator(view_func: Callable): @wraps(view_func) def _wrapped(request, *args, **kwargs): if not request.user.is_authenticated: return redirect_to_login(request.get_full_path(), login_url=reverse(login_view)) # Normalize permissions list perms_list: Iterable[str] = perms or [] allowed = False if any_perm: allowed = any(request.user.has_perm(p) for p in perms_list) if perms_list else True else: # ALL required allowed = request.user.has_perms(perms_list) if perms_list else True if not allowed: msg = message or ( "You do not have the required permission." if len(perms_list) <= 1 else "You do not have the required permissions." ) try: messages.error(request, msg) except Exception: pass # Avoid redirect loops on POST-only endpoints by preferring HTTP_REFERER referer = request.META.get("HTTP_REFERER") if referer: return redirect(referer) # Fallback to a safe dashboard try: return redirect(reverse("admin_frontend:dashboard")) except Exception: return redirect("/") return view_func(request, *args, **kwargs) return _wrapped return decorator