فهرست منبع

Clean up related objects for sites, tenants

jeremystretch 3 سال پیش
والد
کامیت
0c9e7aa074
5فایلهای تغییر یافته به همراه99 افزوده شده و 294 حذف شده
  1. 18 18
      netbox/dcim/views.py
  2. 2 134
      netbox/templates/dcim/site.html
  3. 21 0
      netbox/templates/inc/panels/related_objects.html
  4. 29 119
      netbox/templates/tenancy/tenant.html
  5. 29 23
      netbox/tenancy/views.py

+ 18 - 18
netbox/dcim/views.py

@@ -335,19 +335,25 @@ class SiteView(generic.ObjectView):
     queryset = Site.objects.prefetch_related('tenant__group')
 
     def get_extra_context(self, request, instance):
-        stats = {
-            'location_count': Location.objects.restrict(request.user, 'view').filter(site=instance).count(),
-            'rack_count': Rack.objects.restrict(request.user, 'view').filter(site=instance).count(),
-            'device_count': Device.objects.restrict(request.user, 'view').filter(site=instance).count(),
-            'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(site=instance).count(),
-            'vlangroup_count': VLANGroup.objects.restrict(request.user, 'view').filter(
+        related_models = [
+            # DCIM
+            Location.objects.restrict(request.user, 'view').filter(site=instance),
+            Rack.objects.restrict(request.user, 'view').filter(site=instance),
+            Device.objects.restrict(request.user, 'view').filter(site=instance),
+            # Virtualization
+            VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance),
+            # IPAM
+            Prefix.objects.restrict(request.user, 'view').filter(site=instance),
+            ASN.objects.restrict(request.user, 'view').filter(sites=instance),
+            VLANGroup.objects.restrict(request.user, 'view').filter(
                 scope_type=ContentType.objects.get_for_model(Site),
                 scope_id=instance.pk
-            ).count(),
-            'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(site=instance).count(),
-            'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct().count(),
-            'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance).count(),
-        }
+            ),
+            VLAN.objects.restrict(request.user, 'view').filter(site=instance),
+            # Circuits
+            Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct(),
+        ]
+
         locations = Location.objects.add_related_count(
             Location.objects.all(),
             Rack,
@@ -369,15 +375,9 @@ class SiteView(generic.ObjectView):
             parent_bay__isnull=True
         ).prefetch_related('device_type__manufacturer', 'parent_bay', 'device_role')
 
-        asns = ASN.objects.restrict(request.user, 'view').filter(sites=instance)
-        asn_count = asns.count()
-
-        stats.update({'asn_count': asn_count})
-
         return {
-            'stats': stats,
+            'related_models': related_models,
             'locations': locations,
-            'asns': asns,
             'nonracked_devices': nonracked_devices.order_by('-pk')[:10],
             'total_nonracked_devices_count': nonracked_devices.count(),
         }

+ 2 - 134
netbox/templates/dcim/site.html

@@ -126,112 +126,7 @@
     {% plugin_left_page object %}
     </div>
     <div class="col col-md-6">
-      <div class="card">
-        <h5 class="card-header">Related Objects</h5>
-        <div class="card-body">
-          <table class="table table-hover attr-table">
-            <tr>
-              <th scope="row">Locations</th>
-              <td class="text-end">
-                {% if stats.location_count %}
-                  <a href="{% url 'dcim:location_list' %}?site_id={{ object.pk }}">{{ stats.location_count }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-            </tr>
-            <tr>
-              <th scope="row">Racks</th>
-              <td class="text-end">
-                {% if stats.rack_count %}
-                  <div class="dropdown">
-                    <button class="btn btn-sm btn-light dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
-                      {{ stats.rack_count }}
-                    </button>
-                    <ul class="dropdown-menu">
-                      <li><a class="dropdown-item" href="{% url 'dcim:rack_list' %}?site_id={{ object.pk }}">View Racks</a></li>
-                      <li><a class="dropdown-item" href="{% url 'dcim:rack_elevation_list' %}?site_id={{ object.pk }}">View Elevations</a></li>
-                    </ul>
-                  </div>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-            </tr>
-            <tr>
-              <th scope="row">Devices</th>
-              <td class="text-end">
-                {% if stats.device_count %}
-                  <a href="{% url 'dcim:device_list' %}?site_id={{ object.pk }}">{{ stats.device_count }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-            </tr>
-            <tr>
-              <th scope="row">Virtual Machines</th>
-              <td class="text-end">
-                {% if stats.vm_count %}
-                  <a href="{% url 'virtualization:virtualmachine_list' %}?site_id={{ object.pk }}">{{ stats.vm_count }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-            </tr>
-            <tr>
-              <th scope="row">Prefixes</th>
-              <td class="text-end">
-                {% if stats.prefix_count %}
-                  <a href="{% url 'ipam:prefix_list' %}?site_id={{ object.pk }}">{{ stats.prefix_count }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-            </tr>
-            <tr>
-              <th scope="row">VLAN Groups</th>
-              <td class="text-end">
-                {% if stats.vlangroup_count %}
-                  <a href="{% url 'ipam:vlangroup_list' %}?site={{ object.pk }}">{{ stats.vlangroup_count }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-            </tr>
-            <tr>
-              <th scope="row">VLANs</th>
-              <td class="text-end">
-                {% if stats.vlan_count %}
-                  <a href="{% url 'ipam:vlan_list' %}?site_id={{ object.pk }}">{{ stats.vlan_count }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-            </tr>
-            <tr>
-              <th scope="row">ASNs</th>
-              <td class="text-end">
-                {% if stats.asn_count %}
-                  <a href="{% url 'ipam:asn_list' %}?site_id={{ object.pk }}">{{ stats.asn_count }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-            </tr>
-            <tr>
-              <th scope="row">Circuits</th>
-              <td class="text-end">
-                {% if stats.circuit_count %}
-                <a href="{% url 'circuits:circuit_list' %}?site_id={{ object.pk }}">{{ stats.circuit_count }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-            </tr>
-          </table>
-        </div>
-      </div>
-      {% include 'dcim/inc/nonracked_devices.html' %}
+      {% include 'inc/panels/related_objects.html' with filter_name='site_id' %}
       {% include 'inc/panels/contacts.html' %}
       <div class="card">
         <h5 class="card-header">Locations</h5>
@@ -276,40 +171,13 @@
           </div>
         {% endif %}
       </div>
-      <div class="card">
-        <h5 class="card-header">ASNs</h5>
-        <div class='card-body'>
-          {% if asns %}
-            <table class="table table-hover">
-              <tr>
-                <th>ASN</th>
-                <th>Description</th>
-              </tr>
-            {% for asn in asns %}
-              <tr>
-                <td>{{ asn|linkify }}</td>
-                <td>{{ asn.description|placeholder }}</td>
-              </tr>
-            {% endfor %}
-            </table>
-          {% else %}
-            <span class="text-muted">None</span>
-          {% endif %}
-        </div>
-        {% if perms.ipam.add_asn %}
-          <div class="card-footer text-end noprint">
-            <a href="{% url 'ipam:asn_add' %}?sites={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm">
-              <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add an ASN
-            </a>
-          </div>
-        {% endif %}
-      </div>
       {% include 'inc/panels/image_attachments.html' %}
       {% plugin_right_page object %}
 	</div>
 </div>
 <div class="row">
   <div class="col col-md-12">
+    {% include 'dcim/inc/nonracked_devices.html' %}
     {% plugin_full_width_page object %}
   </div>
 </div>

+ 21 - 0
netbox/templates/inc/panels/related_objects.html

@@ -0,0 +1,21 @@
+{% load helpers %}
+
+<div class="card">
+  <h5 class="card-header">Related Objects</h5>
+  <ul class="list-group list-group-flush">
+    {% for qs in related_models %}
+      {% with viewname=qs.model|viewname:"list" %}
+        <a href="{% url viewname %}?{{ filter_name }}={{ object.pk }}" class="list-group-item list-group-item-action d-flex justify-content-between">
+          {{ qs.model|meta:"verbose_name_plural"|bettertitle }}
+          {% with count=qs.count %}
+            {% if count %}
+              <span class="badge bg-primary rounded-pill">{{ count }}</span>
+            {% else %}
+              <span class="badge bg-light rounded-pill">&mdash;</span>
+            {% endif %}
+          {% endwith %}
+        </a>
+      {% endwith %}
+    {% endfor %}
+  </ul>
+</div>

+ 29 - 119
netbox/templates/tenancy/tenant.html

@@ -10,127 +10,37 @@
 {% endblock breadcrumbs %}
 
 {% block content %}
-<div class="row">
-	<div class="col col-md-7">
-        <div class="card">
-            <h5 class="card-header">
-                Tenant
-            </h5>
-            <div class="card-body">
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <td>Group</td>
-                        <td>{{ object.group|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <td>Description</td>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
+  <div class="row">
+    <div class="col col-md-7">
+      <div class="card">
+        <h5 class="card-header">Tenant</h5>
+        <div class="card-body">
+          <table class="table table-hover attr-table">
+            <tr>
+              <td>Group</td>
+              <td>{{ object.group|linkify|placeholder }}</td>
+            </tr>
+            <tr>
+              <td>Description</td>
+              <td>{{ object.description|placeholder }}</td>
+            </tr>
+          </table>
         </div>
-        {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/tags.html' %}
-        {% include 'inc/panels/comments.html' %}
-        {% include 'inc/panels/contacts.html' %}
-        {% plugin_left_page object %}
-	</div>
-	<div class="col col-md-5">
-        <div class="card">
-            <h5 class="card-header">
-                Stats
-            </h5>
-            <div class="row card-body">
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'dcim:site_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.site_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.site_count }}</a></h2>
-                    <p>Sites</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'dcim:rack_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.rack_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.rack_count }}</a></h2>
-                    <p>Racks</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'dcim:rackreservation_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.rackreservation_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.rackreservation_count }}</a></h2>
-                    <p>Rack reservations</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'dcim:location_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.location_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.location_count }}</a></h2>
-                    <p>Locations</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'dcim:device_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.device_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.device_count }}</a></h2>
-                    <p>Devices</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'dcim:virtualdevicecontext_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.vdc_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.vdc_count }}</a></h2>
-                    <p>Virtual Device Contexts</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'dcim:cable_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.cable_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.cable_count }}</a></h2>
-                    <p>Cables</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'ipam:vrf_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.vrf_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.vrf_count }}</a></h2>
-                    <p>VRFs</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'ipam:aggregate_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.aggregate_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.aggregate_count }}</a></h2>
-                    <p>Aggregates</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'ipam:asn_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.asn_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.asn_count }}</a></h2>
-                    <p>ASNs</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'ipam:prefix_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.prefix_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.prefix_count }}</a></h2>
-                    <p>Prefixes</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'ipam:iprange_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.iprange_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.iprange_count }}</a></h2>
-                    <p>IP Ranges</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'ipam:ipaddress_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.ipaddress_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.ipaddress_count }}</a></h2>
-                    <p>IP addresses</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'ipam:vlan_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.vlan_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.vlan_count }}</a></h2>
-                    <p>VLANs</p>
-                </div>
-
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'ipam:l2vpn_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.l2vpn_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.l2vpn_count }}</a></h2>
-                    <p>L2VPNs</p>
-                </div>
-
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'circuits:circuit_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.circuit_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.circuit_count }}</a></h2>
-                    <p>Circuits</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'virtualization:virtualmachine_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.virtualmachine_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.virtualmachine_count }}</a></h2>
-                    <p>Virtual machines</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'virtualization:cluster_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.cluster_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.cluster_count }}</a></h2>
-                    <p>Clusters</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'wireless:wirelesslan_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.wirelesslan_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.wirelesslan_count }}</a></h2>
-                    <p>Wireless LANs</p>
-                </div>
-                <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'wireless:wirelesslink_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.wirelesslink_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.wirelesslink_count }}</a></h2>
-                    <p>Wireless Links</p>
-                </div>
-            </div>
-        </div>
-        {% plugin_right_page object %}
+      </div>
+      {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/tags.html' %}
+      {% include 'inc/panels/comments.html' %}
+      {% include 'inc/panels/contacts.html' %}
+      {% plugin_left_page object %}
+    </div>
+    <div class="col col-md-5">
+      {% include 'inc/panels/related_objects.html' with filter_name='tenant_id' %}
+      {% plugin_right_page object %}
     </div>
-</div>
-<div class="row">
+  </div>
+  <div class="row">
     <div class="col col-md-12">
-        {% plugin_full_width_page object %}
+      {% plugin_full_width_page object %}
     </div>
-</div>
+  </div>
 {% endblock %}

+ 29 - 23
netbox/tenancy/views.py

@@ -1,5 +1,6 @@
 from django.contrib.contenttypes.models import ContentType
 from django.shortcuts import get_object_or_404
+from django.utils.translation import gettext as _
 
 from circuits.models import Circuit
 from dcim.models import Cable, Device, Location, Rack, RackReservation, Site, VirtualDeviceContext
@@ -92,31 +93,36 @@ class TenantView(generic.ObjectView):
     queryset = Tenant.objects.all()
 
     def get_extra_context(self, request, instance):
-        stats = {
-            'site_count': Site.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'rack_count': Rack.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'rackreservation_count': RackReservation.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'location_count': Location.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'device_count': Device.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'vdc_count': VirtualDeviceContext.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'vrf_count': VRF.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'aggregate_count': Aggregate.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'iprange_count': IPRange.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'ipaddress_count': IPAddress.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'l2vpn_count': L2VPN.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'virtualmachine_count': VirtualMachine.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'cluster_count': Cluster.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'cable_count': Cable.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'asn_count': ASN.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'wirelesslan_count': WirelessLAN.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'wirelesslink_count': WirelessLink.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-        }
+        related_models = [
+            # DCIM
+            Site.objects.restrict(request.user, 'view').filter(tenant=instance),
+            Rack.objects.restrict(request.user, 'view').filter(tenant=instance),
+            RackReservation.objects.restrict(request.user, 'view').filter(tenant=instance),
+            Location.objects.restrict(request.user, 'view').filter(tenant=instance),
+            Device.objects.restrict(request.user, 'view').filter(tenant=instance),
+            VirtualDeviceContext.objects.restrict(request.user, 'view').filter(tenant=instance),
+            Cable.objects.restrict(request.user, 'view').filter(tenant=instance),
+            # IPAM
+            VRF.objects.restrict(request.user, 'view').filter(tenant=instance),
+            Aggregate.objects.restrict(request.user, 'view').filter(tenant=instance),
+            Prefix.objects.restrict(request.user, 'view').filter(tenant=instance),
+            IPRange.objects.restrict(request.user, 'view').filter(tenant=instance),
+            IPAddress.objects.restrict(request.user, 'view').filter(tenant=instance),
+            ASN.objects.restrict(request.user, 'view').filter(tenant=instance),
+            VLAN.objects.restrict(request.user, 'view').filter(tenant=instance),
+            L2VPN.objects.restrict(request.user, 'view').filter(tenant=instance),
+            # Circuits
+            Circuit.objects.restrict(request.user, 'view').filter(tenant=instance),
+            # Virtualization
+            VirtualMachine.objects.restrict(request.user, 'view').filter(tenant=instance),
+            Cluster.objects.restrict(request.user, 'view').filter(tenant=instance),
+            # Wireless
+            WirelessLAN.objects.restrict(request.user, 'view').filter(tenant=instance),
+            WirelessLink.objects.restrict(request.user, 'view').filter(tenant=instance),
+        ]
 
         return {
-            'stats': stats,
+            'related_models': related_models,
         }