from django.contrib.contenttypes.models import ContentType from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.paginator import Paginator from django.db.models import Q from django.shortcuts import redirect from django.views.decorators.http import require_POST from django.views.generic import TemplateView from dcim.models import Location, Region, Site, SiteGroup from ipam.models import IPAddress, Prefix import netaddr from .forms import NetworkOverviewFilterForm from .services import ensure_range_for_ip def _descendant_ids(obj): """Return an object's ID plus nested-tree descendants across NetBox versions.""" try: return list(obj.get_descendants(include_self=True).values_list("pk", flat=True)) except (AttributeError, TypeError): ids = list(obj.get_descendants().values_list("pk", flat=True)) return [obj.pk, *ids] def _scope_query(model, ids): content_type = ContentType.objects.get_for_model(model) return Q(scope_type=content_type, scope_id__in=ids) def apply_overview_filters(queryset, data): if q := data.get("q"): search = Q(description__icontains=q) | Q(comments__icontains=q) try: search |= Q(prefix__net_contains_or_equals=str(netaddr.IPNetwork(q))) except (netaddr.AddrFormatError, ValueError): pass queryset = queryset.filter(search) if tenant := data.get("tenant"): queryset = queryset.filter(tenant=tenant) if group := data.get("tenant_group"): queryset = queryset.filter(tenant__group_id__in=_descendant_ids(group)) if location := data.get("location"): queryset = queryset.filter(_scope_query(Location, _descendant_ids(location))) if site := data.get("site"): location_ids = Location.objects.filter(site=site).values_list("pk", flat=True) queryset = queryset.filter( _scope_query(Site, [site.pk]) | _scope_query(Location, location_ids) ) if site_group := data.get("site_group"): site_ids = Site.objects.filter(group_id__in=_descendant_ids(site_group)).values_list( "pk", flat=True ) location_ids = Location.objects.filter(site_id__in=site_ids).values_list("pk", flat=True) queryset = queryset.filter( _scope_query(SiteGroup, _descendant_ids(site_group)) | _scope_query(Site, site_ids) | _scope_query(Location, location_ids) ) if region := data.get("organization"): region_ids = _descendant_ids(region) site_ids = Site.objects.filter(region_id__in=region_ids).values_list("pk", flat=True) location_ids = Location.objects.filter(site_id__in=site_ids).values_list("pk", flat=True) queryset = queryset.filter( _scope_query(Region, region_ids) | _scope_query(Site, site_ids) | _scope_query(Location, location_ids) ) return queryset class NetworkOverviewView(PermissionRequiredMixin, TemplateView): template_name = "netbox_better_ips/network_overview.html" permission_required = "ipam.view_prefix" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) form = NetworkOverviewFilterForm(self.request.GET or None) prefixes = Prefix.objects.restrict(self.request.user, "view").select_related( "vrf", "tenant", "scope_type" ) if form.is_valid(): prefixes = apply_overview_filters(prefixes, form.cleaned_data) prefixes = prefixes.order_by("prefix", "vrf") paginator = Paginator(prefixes, 50) context.update( { "filter_form": form, "page_obj": paginator.get_page(self.request.GET.get("page")), "total_count": prefixes.count(), } ) return context @require_POST @permission_required(("ipam.view_ipaddress", "ipam.add_iprange"), raise_exception=True) def reconcile_missing_ranges(request): created = skipped = failed = 0 addresses = IPAddress.objects.restrict(request.user, "view").select_related( "tenant", "assigned_object_type" ) for address in addresses.iterator(): try: ip_range, was_created = ensure_range_for_ip(address) created += int(was_created) skipped += int(ip_range is None) except Exception: failed += 1 if failed: messages.warning( request, f"{created} Bereiche wurden angelegt; {failed} IP-Adressen konnten nicht verarbeitet werden.", ) else: messages.success( request, f"{created} fehlende Bereiche wurden angelegt. {skipped} Host-Adressen wurden ignoriert.", ) return redirect("ipam:iprange_list")