| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- from __future__ import annotations
- from django.shortcuts import render, redirect
- from django.contrib import messages
- from django.urls import reverse
- from django.views.decorators.http import require_POST
- from django.contrib.auth.models import Group, Permission
- from django.contrib.auth import get_user_model
- from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
- from django.db import models
- from .models import Organization, OrganizationSite, UserProfile
- from .forms import OrganizationForm, UserSelfForm, UserProfilePhotoForm
- from admin_frontend.templatetags.public_urls import public_route
- from cms.views import breadcrumbs
- def _owner_required(request):
- profile = getattr(request.user, "recycle_profile", None)
- return bool(profile and profile.role == "owner" and (getattr(request, "org", None) is None or profile.organization_id == request.org.id))
- @public_route(label="Organization Settings", order=10, icon="cog-6-tooth")
- @breadcrumbs(label="Organization Settings", name="org_settings")
- def org_settings(request):
- if not request.user.is_authenticated or not _owner_required(request):
- messages.error(request, "Only organization owners can access organization settings.")
- return redirect("admin_frontend:dashboard")
- org = getattr(request, "org", None)
- if not org:
- messages.error(request, "Organization context missing.")
- return redirect("admin_frontend:dashboard")
- if request.method == "POST":
- form = OrganizationForm(request.POST, instance=org)
- if form.is_valid():
- form.save()
- messages.success(request, "Organization updated.")
- return redirect("orgs_admin:org_settings")
- messages.error(request, "Please correct the errors below.")
- else:
- form = OrganizationForm(instance=org)
- return render(request, "orgs/org_settings.html", {"form": form})
- @public_route(label="Organization Sites", order=12, icon="link")
- @breadcrumbs(label="Organization Sites", name="org_sites")
- def org_sites(request):
- if not request.user.is_authenticated or not _owner_required(request):
- messages.error(request, "Only organization owners can access organization sites.")
- return redirect("admin_frontend:dashboard")
- org = getattr(request, "org", None)
- mappings = OrganizationSite.objects.select_related("site").filter(organization=org)
- if request.method == "POST":
- # Simple action: ensure current request.site is mapped to this org
- cur_site = getattr(request, "site", None)
- if not cur_site:
- messages.error(request, "Current site not detected.")
- return redirect("orgs_admin:org_sites")
- mapping, created = OrganizationSite.objects.get_or_create(site=cur_site, defaults={"organization": org})
- if not created and mapping.organization_id != org.id:
- messages.error(request, "This site is already mapped to another organization.")
- else:
- messages.success(request, "Current site mapped to this organization.")
- return redirect("orgs_admin:org_sites")
- return render(request, "orgs/org_sites.html", {"mappings": mappings, "current_site": getattr(request, "site", None)})
- ## ProvidedService management moved to recycle_core views
- # Permissions management ------------------------------------------------------
- ROLE_GROUPS = ["owner", "manager", "driver", "customer", "auditor"]
- APP_WHITELIST = {"recycle_core", "billing", "orgs"}
- @public_route(label="Permissions", order=14, icon="key")
- @breadcrumbs(label="Permissions", name="org_permissions")
- def permissions_overview(request):
- if not request.user.is_authenticated or not _owner_required(request):
- messages.error(request, "Only organization owners can manage permissions.")
- return redirect("admin_frontend:dashboard")
- groups = list(Group.objects.filter(name__in=ROLE_GROUPS).order_by("name"))
- # Ensure missing role groups are visible
- existing = {g.name for g in groups}
- for name in ROLE_GROUPS:
- if name not in existing:
- g = Group.objects.create(name=name)
- groups.append(g)
- groups.sort(key=lambda g: ROLE_GROUPS.index(g.name) if g.name in ROLE_GROUPS else 999)
- return render(request, "orgs/permissions_list.html", {"groups": groups})
- @breadcrumbs(label="Edit Group Permissions", parent="org_permissions")
- def permissions_edit_group(request, pk: int):
- if not request.user.is_authenticated or not _owner_required(request):
- messages.error(request, "Only organization owners can manage permissions.")
- return redirect("admin_frontend:dashboard")
- group = Group.objects.filter(pk=pk).first()
- if not group:
- messages.error(request, "Group not found.")
- return redirect("orgs_admin:permissions_overview")
- # Only allow editing for known role groups to avoid unexpected global changes
- if group.name not in ROLE_GROUPS:
- messages.error(request, "Only role groups can be edited here.")
- return redirect("orgs_admin:permissions_overview")
- perms_all = Permission.objects.select_related("content_type").all()
- perms_all = [p for p in perms_all if p.content_type.app_label in APP_WHITELIST]
- # Build structured grouping: app -> [ {model_key, model_label, perms:[...]}, ... ]
- grouped_apps = {}
- def perm_order_key(codename: str) -> tuple[int, str]:
- # Standard Django perms first in this order, then custom alphabetically
- prefix_order = {"view": 0, "add": 1, "change": 2, "delete": 3}
- prefix = codename.split("_", 1)[0] if "_" in codename else codename
- return (prefix_order.get(prefix, 9), codename)
- def model_sort_key(app_label: str, model_key: str, model_label: str) -> tuple[int, str]:
- if app_label == "billing":
- pref = {"invoice": 0, "invoiceline": 1, "payment": 2, "payout": 3}
- return (pref.get(model_key, 9), model_label)
- return (0, model_label)
- for p in perms_all:
- app = p.content_type.app_label
- model_key = p.content_type.model # e.g., 'invoice', 'invoiceline'
- # Use content_type.name for human label (e.g., 'invoice line')
- model_label = p.content_type.name.title()
- app_bucket = grouped_apps.setdefault(app, {})
- bucket = app_bucket.setdefault(model_key, {"model_key": model_key, "model_label": model_label, "perms": []})
- bucket["perms"].append(p)
- # Convert inner dicts to sorted lists
- for app, models_map in list(grouped_apps.items()):
- models_list = list(models_map.values())
- # Sort perms within model
- for m in models_list:
- m["perms"].sort(key=lambda pr: perm_order_key(pr.codename))
- # Sort models by preference
- models_list.sort(key=lambda m: model_sort_key(app, m["model_key"], m["model_label"]))
- grouped_apps[app] = models_list
- if request.method == "POST":
- sel_ids = request.POST.getlist("perm_ids")
- try:
- sel_ids = [int(x) for x in sel_ids]
- except Exception:
- sel_ids = []
- # Only set within whitelist
- allowed_ids = {p.id for p in perms_all}
- final_ids = [pid for pid in sel_ids if pid in allowed_ids]
- group.permissions.set(Permission.objects.filter(id__in=final_ids))
- messages.success(request, f"Permissions updated for group '{group.name}'.")
- # Redirect back to the current URL (stay on the edit page)
- return redirect(request.get_full_path())
- current_ids = set(group.permissions.values_list("id", flat=True))
- return render(
- request,
- "orgs/permissions_edit.html",
- {"group": group, "grouped_apps": grouped_apps, "current_ids": current_ids},
- )
- @breadcrumbs(label="Group Users", parent="org_permissions")
- def permissions_group_users(request, pk: int):
- if not request.user.is_authenticated or not _owner_required(request):
- messages.error(request, "Only organization owners can view group users.")
- return redirect("admin_frontend:dashboard")
- group = Group.objects.filter(pk=pk).first()
- if not group:
- messages.error(request, "Group not found.")
- return redirect("orgs_admin:permissions_overview")
- User = get_user_model()
- qs = User.objects.filter(groups=group).order_by("username")
- # Scope to current organization if present
- org = getattr(request, "org", None)
- if org is not None:
- qs = qs.filter(recycle_profile__organization=org)
- # Filters
- q = (request.GET.get("q") or "").strip()
- role = (request.GET.get("role") or "").strip()
- if q:
- qs = qs.filter(models.Q(username__icontains=q) | models.Q(email__icontains=q))
- if role:
- qs = qs.filter(recycle_profile__role=role)
- qs = qs.select_related("recycle_profile", "recycle_profile__organization")
- # Pagination
- paginator = Paginator(qs, 15)
- page = request.GET.get("page")
- try:
- page_obj = paginator.page(page)
- except PageNotAnInteger:
- page_obj = paginator.page(1)
- except EmptyPage:
- page_obj = paginator.page(paginator.num_pages)
- role_choices = list(UserProfile.ROLE_CHOICES)
- return render(
- request,
- "orgs/permissions_group_users.html",
- {"group": group, "users": page_obj.object_list, "page_obj": page_obj, "q": q, "role": role, "role_choices": role_choices},
- )
- # ---------------------------------------------------------------------------
- # Self profile editing (current authenticated user)
- @breadcrumbs(label="My Profile", name="my_profile")
- def my_profile(request):
- if not request.user.is_authenticated:
- messages.error(request, "Please sign in to manage your profile.")
- return redirect("admin_frontend:login")
- user = request.user
- user_profile = getattr(user, "recycle_profile", None)
- if request.method == "POST":
- form_user = UserSelfForm(request.POST, instance=user)
- form_photo = UserProfilePhotoForm(request.POST, request.FILES, instance=user_profile) if user_profile else None
- ok_user = form_user.is_valid()
- ok_photo = True if form_photo is None else form_photo.is_valid()
- if ok_user and ok_photo:
- form_user.save()
- if form_photo is not None:
- form_photo.save()
- messages.success(request, "Profile updated.")
- return redirect(reverse("orgs_admin:my_profile"))
- messages.error(request, "Please correct the errors below.")
- else:
- form_user = UserSelfForm(instance=user)
- form_photo = UserProfilePhotoForm(instance=user_profile) if user_profile else None
- return render(
- request,
- "orgs/my_profile.html",
- {"form_user": form_user, "form_photo": form_photo, "has_profile": bool(user_profile)},
- )
|