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)}, )