暫無描述

views_admin.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. from __future__ import annotations
  2. from django.shortcuts import render, redirect
  3. from django.contrib import messages
  4. from django.urls import reverse
  5. from django.views.decorators.http import require_POST
  6. from django.contrib.auth.models import Group, Permission
  7. from django.contrib.auth import get_user_model
  8. from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
  9. from django.db import models
  10. from .models import Organization, OrganizationSite, UserProfile
  11. from .forms import OrganizationForm, UserSelfForm, UserProfilePhotoForm
  12. from admin_frontend.templatetags.public_urls import public_route
  13. from cms.views import breadcrumbs
  14. def _owner_required(request):
  15. profile = getattr(request.user, "recycle_profile", None)
  16. return bool(profile and profile.role == "owner" and (getattr(request, "org", None) is None or profile.organization_id == request.org.id))
  17. @public_route(label="Organization Settings", order=10, icon="cog-6-tooth")
  18. @breadcrumbs(label="Organization Settings", name="org_settings")
  19. def org_settings(request):
  20. if not request.user.is_authenticated or not _owner_required(request):
  21. messages.error(request, "Only organization owners can access organization settings.")
  22. return redirect("admin_frontend:dashboard")
  23. org = getattr(request, "org", None)
  24. if not org:
  25. messages.error(request, "Organization context missing.")
  26. return redirect("admin_frontend:dashboard")
  27. if request.method == "POST":
  28. form = OrganizationForm(request.POST, instance=org)
  29. if form.is_valid():
  30. form.save()
  31. messages.success(request, "Organization updated.")
  32. return redirect("orgs_admin:org_settings")
  33. messages.error(request, "Please correct the errors below.")
  34. else:
  35. form = OrganizationForm(instance=org)
  36. return render(request, "orgs/org_settings.html", {"form": form})
  37. @public_route(label="Organization Sites", order=12, icon="link")
  38. @breadcrumbs(label="Organization Sites", name="org_sites")
  39. def org_sites(request):
  40. if not request.user.is_authenticated or not _owner_required(request):
  41. messages.error(request, "Only organization owners can access organization sites.")
  42. return redirect("admin_frontend:dashboard")
  43. org = getattr(request, "org", None)
  44. mappings = OrganizationSite.objects.select_related("site").filter(organization=org)
  45. if request.method == "POST":
  46. # Simple action: ensure current request.site is mapped to this org
  47. cur_site = getattr(request, "site", None)
  48. if not cur_site:
  49. messages.error(request, "Current site not detected.")
  50. return redirect("orgs_admin:org_sites")
  51. mapping, created = OrganizationSite.objects.get_or_create(site=cur_site, defaults={"organization": org})
  52. if not created and mapping.organization_id != org.id:
  53. messages.error(request, "This site is already mapped to another organization.")
  54. else:
  55. messages.success(request, "Current site mapped to this organization.")
  56. return redirect("orgs_admin:org_sites")
  57. return render(request, "orgs/org_sites.html", {"mappings": mappings, "current_site": getattr(request, "site", None)})
  58. ## ProvidedService management moved to recycle_core views
  59. # Permissions management ------------------------------------------------------
  60. ROLE_GROUPS = ["owner", "manager", "driver", "customer", "auditor"]
  61. APP_WHITELIST = {"recycle_core", "billing", "orgs"}
  62. @public_route(label="Permissions", order=14, icon="key")
  63. @breadcrumbs(label="Permissions", name="org_permissions")
  64. def permissions_overview(request):
  65. if not request.user.is_authenticated or not _owner_required(request):
  66. messages.error(request, "Only organization owners can manage permissions.")
  67. return redirect("admin_frontend:dashboard")
  68. groups = list(Group.objects.filter(name__in=ROLE_GROUPS).order_by("name"))
  69. # Ensure missing role groups are visible
  70. existing = {g.name for g in groups}
  71. for name in ROLE_GROUPS:
  72. if name not in existing:
  73. g = Group.objects.create(name=name)
  74. groups.append(g)
  75. groups.sort(key=lambda g: ROLE_GROUPS.index(g.name) if g.name in ROLE_GROUPS else 999)
  76. return render(request, "orgs/permissions_list.html", {"groups": groups})
  77. @breadcrumbs(label="Edit Group Permissions", parent="org_permissions")
  78. def permissions_edit_group(request, pk: int):
  79. if not request.user.is_authenticated or not _owner_required(request):
  80. messages.error(request, "Only organization owners can manage permissions.")
  81. return redirect("admin_frontend:dashboard")
  82. group = Group.objects.filter(pk=pk).first()
  83. if not group:
  84. messages.error(request, "Group not found.")
  85. return redirect("orgs_admin:permissions_overview")
  86. # Only allow editing for known role groups to avoid unexpected global changes
  87. if group.name not in ROLE_GROUPS:
  88. messages.error(request, "Only role groups can be edited here.")
  89. return redirect("orgs_admin:permissions_overview")
  90. perms_all = Permission.objects.select_related("content_type").all()
  91. perms_all = [p for p in perms_all if p.content_type.app_label in APP_WHITELIST]
  92. # Build structured grouping: app -> [ {model_key, model_label, perms:[...]}, ... ]
  93. grouped_apps = {}
  94. def perm_order_key(codename: str) -> tuple[int, str]:
  95. # Standard Django perms first in this order, then custom alphabetically
  96. prefix_order = {"view": 0, "add": 1, "change": 2, "delete": 3}
  97. prefix = codename.split("_", 1)[0] if "_" in codename else codename
  98. return (prefix_order.get(prefix, 9), codename)
  99. def model_sort_key(app_label: str, model_key: str, model_label: str) -> tuple[int, str]:
  100. if app_label == "billing":
  101. pref = {"invoice": 0, "invoiceline": 1, "payment": 2, "payout": 3}
  102. return (pref.get(model_key, 9), model_label)
  103. return (0, model_label)
  104. for p in perms_all:
  105. app = p.content_type.app_label
  106. model_key = p.content_type.model # e.g., 'invoice', 'invoiceline'
  107. # Use content_type.name for human label (e.g., 'invoice line')
  108. model_label = p.content_type.name.title()
  109. app_bucket = grouped_apps.setdefault(app, {})
  110. bucket = app_bucket.setdefault(model_key, {"model_key": model_key, "model_label": model_label, "perms": []})
  111. bucket["perms"].append(p)
  112. # Convert inner dicts to sorted lists
  113. for app, models_map in list(grouped_apps.items()):
  114. models_list = list(models_map.values())
  115. # Sort perms within model
  116. for m in models_list:
  117. m["perms"].sort(key=lambda pr: perm_order_key(pr.codename))
  118. # Sort models by preference
  119. models_list.sort(key=lambda m: model_sort_key(app, m["model_key"], m["model_label"]))
  120. grouped_apps[app] = models_list
  121. if request.method == "POST":
  122. sel_ids = request.POST.getlist("perm_ids")
  123. try:
  124. sel_ids = [int(x) for x in sel_ids]
  125. except Exception:
  126. sel_ids = []
  127. # Only set within whitelist
  128. allowed_ids = {p.id for p in perms_all}
  129. final_ids = [pid for pid in sel_ids if pid in allowed_ids]
  130. group.permissions.set(Permission.objects.filter(id__in=final_ids))
  131. messages.success(request, f"Permissions updated for group '{group.name}'.")
  132. # Redirect back to the current URL (stay on the edit page)
  133. return redirect(request.get_full_path())
  134. current_ids = set(group.permissions.values_list("id", flat=True))
  135. return render(
  136. request,
  137. "orgs/permissions_edit.html",
  138. {"group": group, "grouped_apps": grouped_apps, "current_ids": current_ids},
  139. )
  140. @breadcrumbs(label="Group Users", parent="org_permissions")
  141. def permissions_group_users(request, pk: int):
  142. if not request.user.is_authenticated or not _owner_required(request):
  143. messages.error(request, "Only organization owners can view group users.")
  144. return redirect("admin_frontend:dashboard")
  145. group = Group.objects.filter(pk=pk).first()
  146. if not group:
  147. messages.error(request, "Group not found.")
  148. return redirect("orgs_admin:permissions_overview")
  149. User = get_user_model()
  150. qs = User.objects.filter(groups=group).order_by("username")
  151. # Scope to current organization if present
  152. org = getattr(request, "org", None)
  153. if org is not None:
  154. qs = qs.filter(recycle_profile__organization=org)
  155. # Filters
  156. q = (request.GET.get("q") or "").strip()
  157. role = (request.GET.get("role") or "").strip()
  158. if q:
  159. qs = qs.filter(models.Q(username__icontains=q) | models.Q(email__icontains=q))
  160. if role:
  161. qs = qs.filter(recycle_profile__role=role)
  162. qs = qs.select_related("recycle_profile", "recycle_profile__organization")
  163. # Pagination
  164. paginator = Paginator(qs, 15)
  165. page = request.GET.get("page")
  166. try:
  167. page_obj = paginator.page(page)
  168. except PageNotAnInteger:
  169. page_obj = paginator.page(1)
  170. except EmptyPage:
  171. page_obj = paginator.page(paginator.num_pages)
  172. role_choices = list(UserProfile.ROLE_CHOICES)
  173. return render(
  174. request,
  175. "orgs/permissions_group_users.html",
  176. {"group": group, "users": page_obj.object_list, "page_obj": page_obj, "q": q, "role": role, "role_choices": role_choices},
  177. )
  178. # ---------------------------------------------------------------------------
  179. # Self profile editing (current authenticated user)
  180. @breadcrumbs(label="My Profile", name="my_profile")
  181. def my_profile(request):
  182. if not request.user.is_authenticated:
  183. messages.error(request, "Please sign in to manage your profile.")
  184. return redirect("admin_frontend:login")
  185. user = request.user
  186. user_profile = getattr(user, "recycle_profile", None)
  187. if request.method == "POST":
  188. form_user = UserSelfForm(request.POST, instance=user)
  189. form_photo = UserProfilePhotoForm(request.POST, request.FILES, instance=user_profile) if user_profile else None
  190. ok_user = form_user.is_valid()
  191. ok_photo = True if form_photo is None else form_photo.is_valid()
  192. if ok_user and ok_photo:
  193. form_user.save()
  194. if form_photo is not None:
  195. form_photo.save()
  196. messages.success(request, "Profile updated.")
  197. return redirect(reverse("orgs_admin:my_profile"))
  198. messages.error(request, "Please correct the errors below.")
  199. else:
  200. form_user = UserSelfForm(instance=user)
  201. form_photo = UserProfilePhotoForm(instance=user_profile) if user_profile else None
  202. return render(
  203. request,
  204. "orgs/my_profile.html",
  205. {"form_user": form_user, "form_photo": form_photo, "has_profile": bool(user_profile)},
  206. )