from __future__ import annotations import json from typing import Any, Dict, List, Literal from django import forms from django.contrib.auth.models import Group, Permission from django.core.paginator import Paginator from django.db.models import Q from django.http import HttpRequest, HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, redirect from django.views import View from django.views.generic.detail import SingleObjectMixin from django.views.generic.edit import DeleteView as DjangoDeleteView from django.views.generic.edit import FormMixin from django.views.generic.list import MultipleObjectMixin from inertia import render from labs.models import User from .base import PermissionRequiredAdminMixin class GroupForm(forms.ModelForm): permissions = forms.ModelMultipleChoiceField( queryset=Permission.objects.all(), required=False ) class Meta: model = Group fields = ["name", "permissions"] class GroupListView(PermissionRequiredAdminMixin, MultipleObjectMixin, View): """View for listing groups.""" model = Group permission_required = "auth.view_group" paginate_by = 10 page_kwarg = "page" ordering = "name" search_fields = ["name"] component = "groups/Index" def get_queryset(self): queryset = self.model._default_manager.all() if self.ordering: queryset = queryset.order_by(self.ordering) query = self.request.GET.get("q", "").strip() if query: conditions = Q() for field in self.search_fields: conditions |= Q(**{f"{field}__icontains": query}) queryset = queryset.filter(conditions) return queryset def serialize_group(self, group: Group) -> Dict[str, Any]: return { "id": group.id, "name": group.name, "user_count": group.user_set.count(), } def get(self, request: HttpRequest) -> HttpResponse: self.object_list = self.get_queryset() paginate_by = self.get_paginate_by(self.object_list) if paginate_by: paginator, page, object_list, is_paginated = self.paginate_queryset( self.object_list, paginate_by ) else: paginator = None page = None object_list = self.object_list is_paginated = False groups = [self.serialize_group(g) for g in object_list] pagination = None if paginator and page: pagination = { "page": page.number, "per_page": paginator.per_page, "total": paginator.count, "num_pages": paginator.num_pages, "is_paginated": is_paginated, } return render( request, self.component, { "groups": groups, "filters": { "q": request.GET.get("q", "").strip(), }, "pagination": pagination, }, ) class GroupCreateView(PermissionRequiredAdminMixin, View): """View for creating a new group.""" permission_required = "auth.add_group" form_class = GroupForm component = "groups/Create" def get_available_permissions(self) -> List[Dict[str, Any]]: perms = Permission.objects.select_related("content_type").all() return [ { "id": p.id, "name": f"{p.content_type.app_label} | {p.content_type.model} | {p.name}", } for p in perms ] def get(self, request: HttpRequest) -> HttpResponse: return render( request, self.component, { "available_permissions": self.get_available_permissions(), }, ) def post(self, request: HttpRequest) -> HttpResponse: form = self.form_class(request.POST) if form.is_valid(): group = form.save() request.session["flash_success"] = "Group created successfully" return redirect(f"/admin/groups/{group.id}/") return render( request, self.component, { "errors": form.errors, "form": form.data, "available_permissions": self.get_available_permissions(), }, ) class GroupDetailView(PermissionRequiredAdminMixin, FormMixin, SingleObjectMixin, View): """Detail/Edit view for a single group.""" model = Group permission_required = "auth.view_group" form_class = GroupForm component = "groups/Detail" def serialize_group(self, group: Group) -> Dict[str, Any]: return { "id": group.id, "name": group.name, "permissions": list(group.permissions.values_list("id", flat=True)), } def get_available_permissions(self) -> List[Dict[str, Any]]: perms = Permission.objects.select_related("content_type").all() return [ { "id": p.id, "name": f"{p.content_type.app_label} | {p.content_type.model} | {p.name}", } for p in perms ] def get_users_context(self, request: HttpRequest) -> Dict[str, Any]: group = self.get_object() users = group.user_set.all().order_by("-date_joined") paginator = Paginator(users, 10) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) return { "users": { "data": [ { "id": u.id, "email": u.email, "name": u.name, "date_joined": u.date_joined.isoformat(), } for u in page_obj ], "pagination": { "page": page_obj.number, "per_page": paginator.per_page, "total": paginator.count, "num_pages": paginator.num_pages, }, } } def get_form_kwargs(self) -> Dict[str, Any]: kwargs = super().get_form_kwargs() kwargs["instance"] = self.object return kwargs def get(self, request: HttpRequest, pk: int) -> HttpResponse: self.object = self.get_object() tab = request.GET.get("tab", "info") context = { "group": self.serialize_group(self.object), "tab": tab, } if tab == "info": context["available_permissions"] = self.get_available_permissions() elif tab == "users": context.update(self.get_users_context(request)) return render(request, self.component, context) def post(self, request: HttpRequest, pk: int) -> HttpResponse: if not request.user.has_perm("auth.change_group"): return redirect(f"/admin/groups/{pk}/") self.object = self.get_object() form = self.get_form() if form.is_valid(): return self.form_valid(form) return self.form_invalid(form) def form_valid(self, form: forms.ModelForm) -> HttpResponse: form.save() self.request.session["flash_success"] = "Group updated successfully" return redirect(f"/admin/groups/{self.object.id}/") def form_invalid(self, form: forms.ModelForm) -> HttpResponse: return render( self.request, self.component, { "group": self.serialize_group(self.object), "errors": form.errors, "form": form.data, "available_permissions": self.get_available_permissions(), }, ) class GroupAddUserView(PermissionRequiredAdminMixin, View): """View to add a user to a group.""" permission_required = "auth.change_group" def post(self, request: HttpRequest, pk: int) -> HttpResponse: group = get_object_or_404(Group, pk=pk) try: data = json.loads(request.body) user_id = data.get("user_id") except (json.JSONDecodeError, AttributeError): return JsonResponse({"error": "Invalid JSON"}, status=400) if not user_id: return JsonResponse({"error": "user_id is required"}, status=400) user = get_object_or_404(User, pk=user_id) group.user_set.add(user) request.session["flash_success"] = f"User {user.email} added to group" return redirect(f"/admin/groups/{pk}/?tab=users") class GroupRemoveUserView(PermissionRequiredAdminMixin, View): """View to remove a user from a group.""" permission_required = "auth.change_group" def post(self, request: HttpRequest, pk: int) -> HttpResponse: group = get_object_or_404(Group, pk=pk) try: data = json.loads(request.body) user_id = data.get("user_id") except (json.JSONDecodeError, AttributeError): return JsonResponse({"error": "Invalid JSON"}, status=400) if not user_id: return JsonResponse({"error": "user_id is required"}, status=400) user = get_object_or_404(User, pk=user_id) group.user_set.remove(user) request.session["flash_success"] = f"User {user.email} removed from group" return redirect(f"/admin/groups/{pk}/?tab=users") class GroupDeleteView(PermissionRequiredAdminMixin, DjangoDeleteView): """View for deleting a group.""" model = Group permission_required = "auth.delete_group" success_url = "/admin/groups/" component = "groups/Delete" def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: self.object = self.get_object() return render( request, self.component, { "group": { "id": self.object.id, "name": self.object.name, "user_count": self.object.user_set.count(), }, }, ) def delete(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: self.object = self.get_object() success_url = self.get_success_url() self.object.delete() request.session["flash_success"] = "Group deleted successfully" return redirect(success_url)