No Description

decorators.py 2.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. from __future__ import annotations
  2. from functools import wraps
  3. from typing import Iterable, Callable, Optional
  4. from django.contrib import messages
  5. from django.contrib.auth.views import redirect_to_login
  6. from django.shortcuts import redirect
  7. from django.urls import reverse
  8. def permissions_required(*perms: str, any_perm: bool = False, message: Optional[str] = None, login_view: str = "admin_frontend:login"):
  9. """Decorator to enforce one or more Django permissions on a view.
  10. - If the user is not authenticated, redirects to the admin login, preserving next.
  11. - If permissions fail, adds an error message and redirects back to the current URL.
  12. Usage:
  13. @permissions_required('recycle_core.assign_driver')
  14. def my_view(...):
  15. ...
  16. @permissions_required('app.perm_a', 'app.perm_b') # ALL required
  17. def another(...):
  18. ...
  19. @permissions_required('app.perm_a', 'app.perm_b', any_perm=True) # ANY required
  20. def another(...):
  21. ...
  22. """
  23. def decorator(view_func: Callable):
  24. @wraps(view_func)
  25. def _wrapped(request, *args, **kwargs):
  26. if not request.user.is_authenticated:
  27. return redirect_to_login(request.get_full_path(), login_url=reverse(login_view))
  28. # Normalize permissions list
  29. perms_list: Iterable[str] = perms or []
  30. allowed = False
  31. if any_perm:
  32. allowed = any(request.user.has_perm(p) for p in perms_list) if perms_list else True
  33. else:
  34. # ALL required
  35. allowed = request.user.has_perms(perms_list) if perms_list else True
  36. if not allowed:
  37. msg = message or (
  38. "You do not have the required permission." if len(perms_list) <= 1
  39. else "You do not have the required permissions."
  40. )
  41. try:
  42. messages.error(request, msg)
  43. except Exception:
  44. pass
  45. # Avoid redirect loops on POST-only endpoints by preferring HTTP_REFERER
  46. referer = request.META.get("HTTP_REFERER")
  47. if referer:
  48. return redirect(referer)
  49. # Fallback to a safe dashboard
  50. try:
  51. return redirect(reverse("admin_frontend:dashboard"))
  52. except Exception:
  53. return redirect("/")
  54. return view_func(request, *args, **kwargs)
  55. return _wrapped
  56. return decorator