Parcourir la source

Introduce child prefixes view for aggregates

jeremystretch il y a 4 ans
Parent
commit
85b10b59e4

+ 6 - 0
netbox/ipam/models/ip.py

@@ -195,6 +195,12 @@ class Aggregate(PrimaryModel):
             return self.prefix.version
         return None
 
+    def get_child_prefixes(self):
+        """
+        Return all Prefixes within this Aggregate
+        """
+        return Prefix.objects.filter(prefix__net_contained=str(self.prefix))
+
     def get_utilization(self):
         """
         Determine the prefix utilization of the aggregate and return it as a percentage.

+ 1 - 0
netbox/ipam/urls.py

@@ -61,6 +61,7 @@ urlpatterns = [
     path('aggregates/edit/', views.AggregateBulkEditView.as_view(), name='aggregate_bulk_edit'),
     path('aggregates/delete/', views.AggregateBulkDeleteView.as_view(), name='aggregate_bulk_delete'),
     path('aggregates/<int:pk>/', views.AggregateView.as_view(), name='aggregate'),
+    path('aggregates/<int:pk>/prefixes/', views.AggregatePrefixesView.as_view(), name='aggregate_prefixes'),
     path('aggregates/<int:pk>/edit/', views.AggregateEditView.as_view(), name='aggregate_edit'),
     path('aggregates/<int:pk>/delete/', views.AggregateDeleteView.as_view(), name='aggregate_delete'),
     path('aggregates/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='aggregate_changelog', kwargs={'model': Aggregate}),

+ 19 - 26
netbox/ipam/views.py

@@ -275,39 +275,32 @@ class AggregateListView(generic.ObjectListView):
 class AggregateView(generic.ObjectView):
     queryset = Aggregate.objects.all()
 
-    def get_extra_context(self, request, instance):
-        # Find all child prefixes contained in this aggregate
-        prefix_list = Prefix.objects.restrict(request.user, 'view').filter(
-            prefix__net_contained_or_equal=str(instance.prefix)
-        ).prefetch_related(
-            'site', 'role'
-        ).order_by(
-            'prefix'
-        )
 
-        # Return List of requested Prefixes
+class AggregatePrefixesView(generic.ObjectChildrenView):
+    queryset = Aggregate.objects.all()
+    child_model = Prefix
+    table = tables.PrefixTable
+    filterset = filtersets.PrefixFilterSet
+    template_name = 'ipam/aggregate/prefixes.html'
+
+    def get_children(self, request, parent):
+        return Prefix.objects.restrict(request.user, 'view').filter(
+            prefix__net_contained_or_equal=str(parent.prefix)
+        ).prefetch_related('site', 'role', 'tenant', 'vlan')
+
+    def prep_table_data(self, request, queryset, parent):
+        # Determine whether to show assigned prefixes, available prefixes, or both
         show_available = bool(request.GET.get('show_available', 'true') == 'true')
         show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
-        child_prefixes = add_requested_prefixes(instance.prefix, prefix_list, show_available, show_assigned)
-
-        prefix_table = tables.PrefixTable(child_prefixes, exclude=('utilization',))
-        if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
-            prefix_table.columns.show('pk')
-        paginate_table(prefix_table, request)
 
-        # Compile permissions list for rendering the object table
-        permissions = {
-            'add': request.user.has_perm('ipam.add_prefix'),
-            'change': request.user.has_perm('ipam.change_prefix'),
-            'delete': request.user.has_perm('ipam.delete_prefix'),
-        }
+        return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)
 
+    def get_extra_context(self, request, instance):
         return {
-            'prefix_table': prefix_table,
-            'permissions': permissions,
             'bulk_querystring': f'within={instance.prefix}',
-            'show_available': show_available,
-            'show_assigned': show_assigned,
+            'active_tab': 'prefixes',
+            'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
+            'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
         }
 
 

+ 53 - 69
netbox/templates/ipam/aggregate.html

@@ -1,82 +1,66 @@
-{% extends 'generic/object.html' %}
+{% extends 'ipam/aggregate/base.html' %}
 {% load buttons %}
 {% load helpers %}
 {% load plugins %}
 
-{% block breadcrumbs %}
-  {{ block.super }}
-  <li class="breadcrumb-item"><a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.rir.pk }}">{{ object.rir }}</a></li>
-{% endblock %}
-
-{% block extra_controls %}
-  {% include 'ipam/inc/toggle_available.html' %}
-{% endblock %}
-
 {% block content %}
-<div class="row">
-	<div class="col col-md-6">
-        <div class="card">
-            <h5 class="card-header">
-                Aggregate
-            </h5>
-            <div class="card-body">
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <td>Family</td>
-                        <td>IPv{{ object.family }}</td>
-                    </tr>
-                    <tr>
-                        <td>RIR</td>
-                        <td>
-                            <a href="{% url 'ipam:aggregate_list' %}?rir={{ object.rir.slug }}">{{ object.rir }}</a>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>Utilization</td>
-                        <td>
-                            {% utilization_graph object.get_utilization %}
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>Tenant</td>
-                        <td>
-                            {% if object.tenant %}
-                                {% if prefix.object.group %}
-                                    <a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
-                                {% endif %}
-                                <a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
-                            {% else %}
-                                <span class="text-muted">None</span>
-                            {% endif %}
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>Date Added</td>
-                        <td>{{ object.date_added|annotated_date|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <td>Description</td>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
+  <div class="row">
+    <div class="col col-md-6">
+      <div class="card">
+        <h5 class="card-header">Aggregate</h5>
+        <div class="card-body">
+          <table class="table table-hover attr-table">
+            <tr>
+              <td>Family</td>
+              <td>IPv{{ object.family }}</td>
+            </tr>
+            <tr>
+              <td>RIR</td>
+              <td>
+                <a href="{% url 'ipam:aggregate_list' %}?rir={{ object.rir.slug }}">{{ object.rir }}</a>
+              </td>
+            </tr>
+            <tr>
+              <td>Utilization</td>
+              <td>
+                {% utilization_graph object.get_utilization %}
+              </td>
+            </tr>
+            <tr>
+              <td>Tenant</td>
+              <td>
+                {% if object.tenant %}
+                  {% if prefix.object.group %}
+                    <a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
+                  {% endif %}
+                  <a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
+                {% else %}
+                  <span class="text-muted">None</span>
+                {% endif %}
+              </td>
+            </tr>
+            <tr>
+              <td>Date Added</td>
+              <td>{{ object.date_added|annotated_date|placeholder }}</td>
+            </tr>
+            <tr>
+              <td>Description</td>
+              <td>{{ object.description|placeholder }}</td>
+            </tr>
+          </table>
         </div>
-        {% plugin_left_page object %}
+      </div>
+      {% plugin_left_page object %}
     </div>
     <div class="col col-md-6">
-        {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/tags.html' %}
-        {% plugin_right_page object %}
+      {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/tags.html' %}
+      {% plugin_right_page object %}
     </div>
-</div>
-<div class="row mb-3">
+  </div>
+  <div class="row mb-3">
     <div class="col col-md-12">
-        {% plugin_full_width_page object %}
+      {% plugin_full_width_page object %}
     </div>
-</div>
-<div class="row mb-3">
-  <div class="col col-md-12">
-    {% include 'utilities/obj_table.html' with table=prefix_table heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
   </div>
-</div>
 {% endblock %}

+ 23 - 0
netbox/templates/ipam/aggregate/base.html

@@ -0,0 +1,23 @@
+{% extends 'generic/object.html' %}
+{% load buttons %}
+{% load helpers %}
+
+{% block breadcrumbs %}
+  {{ block.super }}
+  <li class="breadcrumb-item"><a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.rir.pk }}">{{ object.rir }}</a></li>
+{% endblock %}
+
+{% block tab_items %}
+  <li role="presentation" class="nav-item">
+    <a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">
+      Aggregate
+    </a>
+  </li>
+  {% if perms.ipam.view_prefix %}
+    <li role="presentation" class="nav-item">
+      <a class="nav-link{% if active_tab == 'prefixes' %} active{% endif %}" href="{% url 'ipam:aggregate_prefixes' pk=object.pk %}">
+        Prefixes {% badge object.get_child_prefixes.count %}
+      </a>
+    </li>
+  {% endif %}
+{% endblock %}

+ 36 - 0
netbox/templates/ipam/aggregate/prefixes.html

@@ -0,0 +1,36 @@
+{% extends 'ipam/aggregate/base.html' %}
+{% load helpers %}
+
+{% block extra_controls %}
+  {% include 'ipam/inc/toggle_available.html' %}
+  {{ block.super }}
+{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% include 'inc/table_controls_htmx.html' with table_modal="PrefixTable_config" %}
+
+    <div class="card">
+      <div class="card-body" id="object_list">
+        {% include 'htmx/table.html' %}
+      </div>
+    </div>
+
+    <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if perms.ipam.change_prefix %}
+          <button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm">
+            <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+          </button>
+        {% endif %}
+        {% if perms.ipam.delete_prefix %}
+          <button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm">
+            <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+          </button>
+        {% endif %}
+      </div>
+    </div>
+  </form>
+  {% table_config_form table %}
+{% endblock %}