Closes #18147: Include device & VM interfaces in VRF related objects (#20158)

This commit is contained in:
Jeremy Stretch 2025-08-22 19:01:34 -04:00 committed by GitHub
parent d5e49c8cb0
commit 66140fc017
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 18 deletions

View File

@ -53,8 +53,26 @@ class VRFView(GetRelatedModelsMixin, generic.ObjectView):
) )
export_targets_table.configure(request) export_targets_table.configure(request)
related_models = self.get_related_models(
request,
instance,
omit=(Interface, VMInterface),
extra=(
(
Interface.objects.restrict(request.user, 'view').filter(vrf=instance),
'vrf_id',
_('Device Interfaces')
),
(
VMInterface.objects.restrict(request.user, 'view').filter(vrf=instance),
'vrf_id',
_('VM Interfaces')
),
),
)
return { return {
'related_models': self.get_related_models(request, instance, omit=[Interface, VMInterface]), 'related_models': related_models,
'import_targets_table': import_targets_table, 'import_targets_table': import_targets_table,
'export_targets_table': export_targets_table, 'export_targets_table': export_targets_table,
} }

View File

@ -4,12 +4,12 @@
<div class="card"> <div class="card">
<h2 class="card-header">{% trans "Related Objects" %}</h2> <h2 class="card-header">{% trans "Related Objects" %}</h2>
<ul class="list-group list-group-flush" role="presentation"> <ul class="list-group list-group-flush" role="presentation">
{% for qs, filter_param in related_models %} {% for related_object_count in related_models %}
{% with viewname=qs.model|validated_viewname:"list" %} {% with viewname=related_object_count.queryset.model|validated_viewname:"list" %}
{% if viewname is not None %} {% if viewname is not None %}
<a href="{% url viewname %}?{{ filter_param }}={{ object.pk }}" class="list-group-item list-group-item-action d-flex justify-content-between"> <a href="{% url viewname %}?{{ related_object_count.filter_param }}={{ object.pk }}" class="list-group-item list-group-item-action d-flex justify-content-between">
{{ qs.model|meta:"verbose_name_plural"|bettertitle }} {{ related_object_count.name }}
{% with count=qs.count %} {% with count=related_object_count.queryset.count %}
{% if count %} {% if count %}
<span class="badge text-bg-primary rounded-pill">{{ count }}</span> <span class="badge text-bg-primary rounded-pill">{{ count }}</span>
{% else %} {% else %}

View File

@ -1,8 +1,10 @@
from dataclasses import dataclass
from typing import Iterable from typing import Iterable
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import AccessMixin from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db.models import QuerySet
from django.urls import reverse from django.urls import reverse
from django.urls.exceptions import NoReverseMatch from django.urls.exceptions import NoReverseMatch
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -12,6 +14,7 @@ from netbox.plugins import PluginConfig
from netbox.registry import registry from netbox.registry import registry
from utilities.relations import get_related_models from utilities.relations import get_related_models
from utilities.request import safe_for_redirect from utilities.request import safe_for_redirect
from utilities.string import title
from .permissions import resolve_permission from .permissions import resolve_permission
__all__ = ( __all__ = (
@ -177,8 +180,17 @@ class GetRelatedModelsMixin:
""" """
Provides logic for collecting all related models for the currently viewed model. Provides logic for collecting all related models for the currently viewed model.
""" """
@dataclass
class RelatedObjectCount:
queryset: QuerySet
filter_param: str
label: str = ''
def get_related_models(self, request, instance, omit=[], extra=[]): @property
def name(self):
return self.label or title(_(self.queryset.model._meta.verbose_name_plural))
def get_related_models(self, request, instance, omit=None, extra=None):
""" """
Get related models of the view's `queryset` model without those listed in `omit`. Will be sorted alphabetical. Get related models of the view's `queryset` model without those listed in `omit`. Will be sorted alphabetical.
@ -191,6 +203,7 @@ class GetRelatedModelsMixin:
extra: Add extra models to the list of automatically determined related models. Can be used to add indirect extra: Add extra models to the list of automatically determined related models. Can be used to add indirect
relationships. relationships.
""" """
omit = omit or []
model = self.queryset.model model = self.queryset.model
related = filter( related = filter(
lambda m: m[0] is not model and m[0] not in omit, lambda m: m[0] is not model and m[0] not in omit,
@ -198,7 +211,7 @@ class GetRelatedModelsMixin:
) )
related_models = [ related_models = [
( self.RelatedObjectCount(
model.objects.restrict(request.user, 'view').filter(**( model.objects.restrict(request.user, 'view').filter(**(
{f'{field}__in': instance} {f'{field}__in': instance}
if isinstance(instance, Iterable) if isinstance(instance, Iterable)
@ -208,11 +221,14 @@ class GetRelatedModelsMixin:
) )
for model, field in related for model, field in related
] ]
related_models.extend(extra) if extra is not None:
related_models.extend([
self.RelatedObjectCount(*attrs) for attrs in extra
])
return sorted( return sorted(
filter(lambda qs: qs[0].exists(), related_models), filter(lambda roc: roc.queryset.exists(), related_models),
key=lambda qs: qs[0].model._meta.verbose_name.lower(), key=lambda roc: roc.name,
) )