from django.contrib import messages from django.contrib.contenttypes.models import ContentType from django.shortcuts import get_object_or_404, redirect, render from django.utils.translation import gettext_lazy as _ from netbox.views import generic from utilities.views import register_model_view from .engine.resolver import Resolver from .filtersets import ServiceFilterSet, ServiceTemplateFilterSet from .forms import ( ResolveForm, ServiceBulkEditForm, ServiceFilterSetForm, ServiceForm, ServiceTemplateBulkEditForm, ServiceTemplateFilterSetForm, ServiceTemplateForm, ServiceTemplateImportForm, ) from .models import Service, ServiceTemplate from .tables import ServiceTable, ServiceTemplateTable def _build_preview(result): """Transform result.planned_objects into a list of dicts with pre-computed action.""" preview = [] for node_name, obj in result.planned_objects: ct = ContentType.objects.get_for_model(obj.__class__) key = (ct.id, obj.pk) action = "add" if key in result.to_add else "keep" preview.append({"node": node_name, "object": obj, "action": action}) return preview # ════════════════════════════════════════════════════════════════════ # Service Template # ════════════════════════════════════════════════════════════════════ @register_model_view(ServiceTemplate, "list", path="", detail=False) class ServiceTemplateListView(generic.ObjectListView): queryset = ServiceTemplate.objects.all() table = ServiceTemplateTable filterset = ServiceTemplateFilterSet filterset_form = ServiceTemplateFilterSetForm @register_model_view(ServiceTemplate, "add", path="add", detail=False) @register_model_view(ServiceTemplate, "edit") class ServiceTemplateEditView(generic.ObjectEditView): queryset = ServiceTemplate.objects.all() form = ServiceTemplateForm @register_model_view(ServiceTemplate, "delete") class ServiceTemplateDeleteView(generic.ObjectDeleteView): queryset = ServiceTemplate.objects.all() @register_model_view(ServiceTemplate, "bulk_edit", path="edit", detail=False) class ServiceTemplateBulkEditView(generic.BulkEditView): queryset = ServiceTemplate.objects.all() table = ServiceTemplateTable form = ServiceTemplateBulkEditForm @register_model_view(ServiceTemplate, "bulk_delete", path="delete", detail=False) class ServiceTemplateBulkDeleteView(generic.BulkDeleteView): queryset = ServiceTemplate.objects.all() table = ServiceTemplateTable @register_model_view(ServiceTemplate, "bulk_import", path="import", detail=False) class ServiceTemplateBulkImportView(generic.BulkImportView): queryset = ServiceTemplate.objects.all() model_form = ServiceTemplateImportForm table = ServiceTemplateTable @register_model_view(ServiceTemplate) class ServiceTemplateView(generic.ObjectView): queryset = ServiceTemplate.objects.all() def get_extra_context(self, request, instance): context = super().get_extra_context(request, instance) context["is_locked"] = instance.is_locked return context # ════════════════════════════════════════════════════════════════════ # Service # ════════════════════════════════════════════════════════════════════ @register_model_view(Service, "list", path="", detail=False) class ServiceListView(generic.ObjectListView): queryset = Service.objects.all() table = ServiceTable filterset = ServiceFilterSet filterset_form = ServiceFilterSetForm @register_model_view(Service, "add", path="add", detail=False) @register_model_view(Service, "edit") class ServiceEditView(generic.ObjectEditView): queryset = Service.objects.all() form = ServiceForm @register_model_view(Service, "delete") class ServiceDeleteView(generic.ObjectDeleteView): queryset = Service.objects.all() @register_model_view(Service, "bulk_edit", path="edit", detail=False) class ServiceBulkEditView(generic.BulkEditView): queryset = Service.objects.all() table = ServiceTable form = ServiceBulkEditForm @register_model_view(Service, "bulk_delete", path="delete", detail=False) class ServiceBulkDeleteView(generic.BulkDeleteView): queryset = Service.objects.all() table = ServiceTable @register_model_view(Service) class ServiceView(generic.ObjectView): queryset = Service.objects.all() def get_extra_context(self, request, instance): context = super().get_extra_context(request, instance) context["bindings"] = instance.bindings.select_related( "content_type", ).all() return context @register_model_view(Service, "resolve", path="resolve") class ServiceResolveView(generic.ObjectView): """Dry-run preview (GET) and commit (POST) for service resolution.""" queryset = Service.objects.all() def get(self, request, pk): service = get_object_or_404(self.queryset, pk=pk) resolver = Resolver() try: result = resolver.resolve(service, mode="dry_run", user=request.user) except Exception as exc: messages.error(request, f"Resolution failed: {exc}") return redirect("plugins:netbox_services:service", pk=service.pk) return render(request, "netbox_services/service_resolve.html", { "object": service, "service": service, "result": result, "planned_preview": _build_preview(result), "form": ResolveForm(initial={"mode": "commit"}), "active_tab": "resolve", }) def post(self, request, pk): service = get_object_or_404(self.queryset, pk=pk) form = ResolveForm(request.POST) mode = form.cleaned_data.get("mode", "dry_run") if form.is_valid() else "dry_run" resolver = Resolver() try: result = resolver.resolve(service, mode=mode, user=request.user) except Exception as exc: messages.error(request, f"Resolution failed: {exc}") return redirect("plugins:netbox_services:service", pk=service.pk) if mode == "commit": messages.success( request, _("Resolution committed: {added} added, {removed} removed, {kept} unchanged.").format( added=len(result.to_add), removed=len(result.to_remove), kept=result.total_planned - len(result.to_add), ), ) return redirect("plugins:netbox_services:service", pk=service.pk) return render(request, "netbox_services/service_resolve.html", { "object": service, "service": service, "result": result, "planned_preview": _build_preview(result), "form": ResolveForm(initial={"mode": "commit"}), "active_tab": "resolve", })