Просмотр исходного кода

Adds contact tabs (#12460)

* adds contact tabs #11599

* fixed lint issues

* changes as per review

* changes as per review

* replaces generic object template with base template
Abhimanyu Saharan 2 лет назад
Родитель
Сommit
4eb5e90ccc

+ 16 - 1
netbox/circuits/views.py

@@ -1,10 +1,10 @@
 from django.contrib import messages
 from django.db import transaction
-from django.db.models import Q
 from django.shortcuts import get_object_or_404, redirect, render
 
 from dcim.views import PathTraceView
 from netbox.views import generic
+from tenancy.views import ObjectContactsView
 from utilities.forms import ConfirmationForm
 from utilities.utils import count_related
 from utilities.views import register_model_view
@@ -73,6 +73,11 @@ class ProviderBulkDeleteView(generic.BulkDeleteView):
     table = tables.ProviderTable
 
 
+@register_model_view(Provider, 'contacts')
+class ProviderContactsView(ObjectContactsView):
+    queryset = Provider.objects.all()
+
+
 #
 # ProviderAccounts
 #
@@ -134,6 +139,11 @@ class ProviderAccountBulkDeleteView(generic.BulkDeleteView):
     table = tables.ProviderAccountTable
 
 
+@register_model_view(ProviderAccount, 'contacts')
+class ProviderAccountContactsView(ObjectContactsView):
+    queryset = ProviderAccount.objects.all()
+
+
 #
 # Provider networks
 #
@@ -389,6 +399,11 @@ class CircuitSwapTerminations(generic.ObjectEditView):
         })
 
 
+@register_model_view(Circuit, 'contacts')
+class CircuitContactsView(ObjectContactsView):
+    queryset = Circuit.objects.all()
+
+
 #
 # Circuit terminations
 #

+ 41 - 0
netbox/dcim/views.py

@@ -20,6 +20,7 @@ from extras.views import ObjectConfigContextView
 from ipam.models import ASN, IPAddress, Prefix, VLAN, VLANGroup
 from ipam.tables import InterfaceVLANTable
 from netbox.views import generic
+from tenancy.views import ObjectContactsView
 from utilities.forms import ConfirmationForm
 from utilities.paginator import EnhancedPaginator, get_paginate_count
 from utilities.permissions import get_permission_for_model
@@ -267,6 +268,11 @@ class RegionBulkDeleteView(generic.BulkDeleteView):
     table = tables.RegionTable
 
 
+@register_model_view(Region, 'contacts')
+class RegionContactsView(ObjectContactsView):
+    queryset = Region.objects.all()
+
+
 #
 # Site groups
 #
@@ -342,6 +348,11 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView):
     table = tables.SiteGroupTable
 
 
+@register_model_view(SiteGroup, 'contacts')
+class SiteGroupContactsView(ObjectContactsView):
+    queryset = SiteGroup.objects.all()
+
+
 #
 # Sites
 #
@@ -435,6 +446,11 @@ class SiteBulkDeleteView(generic.BulkDeleteView):
     table = tables.SiteTable
 
 
+@register_model_view(Site, 'contacts')
+class SiteContactsView(ObjectContactsView):
+    queryset = Site.objects.all()
+
+
 #
 # Locations
 #
@@ -523,6 +539,11 @@ class LocationBulkDeleteView(generic.BulkDeleteView):
     table = tables.LocationTable
 
 
+@register_model_view(Location, 'contacts')
+class LocationContactsView(ObjectContactsView):
+    queryset = Location.objects.all()
+
+
 #
 # Rack roles
 #
@@ -740,6 +761,11 @@ class RackBulkDeleteView(generic.BulkDeleteView):
     table = tables.RackTable
 
 
+@register_model_view(Rack, 'contacts')
+class RackContactsView(ObjectContactsView):
+    queryset = Rack.objects.all()
+
+
 #
 # Rack reservations
 #
@@ -874,6 +900,11 @@ class ManufacturerBulkDeleteView(generic.BulkDeleteView):
     table = tables.ManufacturerTable
 
 
+@register_model_view(Manufacturer, 'contacts')
+class ManufacturerContactsView(ObjectContactsView):
+    queryset = Manufacturer.objects.all()
+
+
 #
 # Device types
 #
@@ -2088,6 +2119,11 @@ class DeviceBulkRenameView(generic.BulkRenameView):
     table = tables.DeviceTable
 
 
+@register_model_view(Device, 'contacts')
+class DeviceContactsView(ObjectContactsView):
+    queryset = Device.objects.all()
+
+
 #
 # Modules
 #
@@ -3469,6 +3505,11 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView):
     table = tables.PowerPanelTable
 
 
+@register_model_view(PowerPanel, 'contacts')
+class PowerPanelContactsView(ObjectContactsView):
+    queryset = PowerPanel.objects.all()
+
+
 #
 # Power feeds
 #

+ 6 - 0
netbox/ipam/views.py

@@ -9,6 +9,7 @@ from circuits.models import Provider
 from dcim.filtersets import InterfaceFilterSet
 from dcim.models import Interface, Site
 from netbox.views import generic
+from tenancy.views import ObjectContactsView
 from utilities.utils import count_related
 from utilities.views import ViewTab, register_model_view
 from virtualization.filtersets import VMInterfaceFilterSet
@@ -1300,6 +1301,11 @@ class L2VPNBulkDeleteView(generic.BulkDeleteView):
     table = tables.L2VPNTable
 
 
+@register_model_view(L2VPN, 'contacts')
+class L2VPNContactsView(ObjectContactsView):
+    queryset = L2VPN.objects.all()
+
+
 #
 # L2VPN terminations
 #

+ 0 - 1
netbox/templates/circuits/circuit.html

@@ -70,7 +70,6 @@
     <div class="col col-md-6">
       {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_a side='A' %}
       {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_z side='Z' %}
-      {% include 'inc/panels/contacts.html' %}
       {% include 'inc/panels/image_attachments.html' %}
       {% plugin_right_page object %}
     </div>

+ 0 - 1
netbox/templates/circuits/provider.html

@@ -43,7 +43,6 @@
     <div class="col col-md-6">
         {% include 'inc/panels/related_objects.html' %}
         {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/contacts.html' %}
         {% plugin_right_page object %}
     </div>
 </div>

+ 0 - 1
netbox/templates/circuits/provideraccount.html

@@ -38,7 +38,6 @@
       {% include 'inc/panels/related_objects.html' %}
       {% include 'inc/panels/comments.html' %}
       {% include 'inc/panels/custom_fields.html' %}
-      {% include 'inc/panels/contacts.html' %}
       {% plugin_right_page object %}
     </div>
     <div class="col col-md-12">

+ 0 - 1
netbox/templates/dcim/device.html

@@ -298,7 +298,6 @@
                 </div>
               {% endif %}
             </div>
-            {% include 'inc/panels/contacts.html' %}
             {% include 'inc/panels/image_attachments.html' %}
             <div class="card">
                 <h5 class="card-header">Dimensions</h5>

+ 0 - 1
netbox/templates/dcim/location.html

@@ -65,7 +65,6 @@
   </div>
 	<div class="col col-md-6">
     {% include 'inc/panels/related_objects.html' %}
-    {% include 'inc/panels/contacts.html' %}
     {% include 'dcim/inc/nonracked_devices.html' %}
     {% include 'inc/panels/image_attachments.html' %}
     {% plugin_right_page object %}

+ 0 - 1
netbox/templates/dcim/manufacturer.html

@@ -51,7 +51,6 @@
 	<div class="col col-md-6">
     {% include 'inc/panels/related_objects.html' %}
     {% include 'inc/panels/custom_fields.html' %}
-    {% include 'inc/panels/contacts.html' %}
     {% plugin_right_page object %}
   </div>
 </div>

+ 0 - 1
netbox/templates/dcim/powerpanel.html

@@ -40,7 +40,6 @@
 	<div class="col col-md-6">
     {% include 'inc/panels/related_objects.html' %}
     {% include 'inc/panels/custom_fields.html' %}
-    {% include 'inc/panels/contacts.html' %}
     {% include 'inc/panels/image_attachments.html' %}
     {% plugin_right_page object %}
   </div>

+ 0 - 1
netbox/templates/dcim/rack.html

@@ -191,7 +191,6 @@
         </div>
         {% include 'inc/panels/related_objects.html' %}
         {% include 'dcim/inc/nonracked_devices.html' %}
-        {% include 'inc/panels/contacts.html' %}
         {% plugin_right_page object %}
     </div>
   </div>

+ 0 - 1
netbox/templates/dcim/region.html

@@ -46,7 +46,6 @@
   </div>
 	<div class="col col-md-6">
     {% include 'inc/panels/related_objects.html' %}
-    {% include 'inc/panels/contacts.html' %}
     {% plugin_right_page object %}
 	</div>
 </div>

+ 0 - 1
netbox/templates/dcim/site.html

@@ -131,7 +131,6 @@
     </div>
     <div class="col col-md-6">
       {% 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>
         <div class='card-body'>

+ 0 - 1
netbox/templates/dcim/sitegroup.html

@@ -42,7 +42,6 @@
     </div>
     {% include 'inc/panels/tags.html' %}
     {% include 'inc/panels/custom_fields.html' %}
-    {% include 'inc/panels/contacts.html' %}
     {% plugin_left_page object %}
   </div>
 	<div class="col col-md-6">

+ 0 - 63
netbox/templates/inc/panels/contacts.html

@@ -1,63 +0,0 @@
-{% load helpers %}
-
-<div class="card">
-  <h5 class="card-header">Contacts</h5>
-  <div class="card-body">
-    {% with contacts=object.contacts.all %}
-      {% if contacts.exists %}
-        <table class="table table-hover">
-          <tr>
-            <th>Name</th>
-            <th>Role</th>
-            <th>Priority</th>
-            <th>Phone</th>
-            <th>Email</th>
-            <th></th>
-          </tr>
-          {% for contact in contacts %}
-            <tr>
-              <td>{{ contact.contact|linkify }}</td>
-              <td>{{ contact.role|placeholder }}</td>
-              <td>{{ contact.get_priority_display|placeholder }}</td>
-              <td>
-                {% if contact.contact.phone %}
-                  <a href="tel:{{ contact.contact.phone }}">{{ contact.contact.phone }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-              <td>
-                {% if contact.contact.email %}
-                  <a href="mailto:{{ contact.contact.email }}">{{ contact.contact.email }}</a>
-                {% else %}
-                  {{ ''|placeholder }}
-                {% endif %}
-              </td>
-              <td class="text-end noprint">
-                {% if perms.tenancy.change_contactassignment %}
-                  <a href="{% url 'tenancy:contactassignment_edit' pk=contact.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-sm lh-1" title="Edit">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i>
-                  </a>
-                {% endif %}
-                {% if perms.tenancy.delete_contactassignment %}
-                  <a href="{% url 'tenancy:contactassignment_delete' pk=contact.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-danger btn-sm lh-1" title="Delete">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
-                  </a>
-                {% endif %}
-              </td>
-            </tr>
-          {% endfor %}
-        </table>
-      {% else %}
-        <div class="text-muted">None</div>
-      {% endif %}
-    {% endwith %}
-  </div>
-  {% if perms.tenancy.add_contactassignment %}
-    <div class="card-footer text-end noprint">
-      <a href="{% url 'tenancy:contactassignment_add' %}?content_type={{ object|content_type_id }}&object_id={{ 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 a contact
-      </a>
-    </div>
-  {% endif %}
-</div>

+ 0 - 1
netbox/templates/ipam/l2vpn.html

@@ -37,7 +37,6 @@
     {% plugin_left_page object %}
 	</div>
 	<div class="col col-md-6">
-      {% include 'inc/panels/contacts.html' %}
       {% include 'inc/panels/custom_fields.html' %}
       {% include 'inc/panels/comments.html' %}
       {% plugin_right_page object %}

+ 27 - 0
netbox/templates/tenancy/object_contacts.html

@@ -0,0 +1,27 @@
+{% extends base_template %}
+{% load helpers %}
+
+{% block extra_controls %}
+    {% if perms.tenancy.add_contactassignment %}
+    <a href="{% url 'tenancy:contactassignment_add' %}?content_type={{ object|content_type_id }}&object_id={{ 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 a contact
+    </a>
+  {% endif %}
+{% endblock %}
+
+{% block content %}
+    {% include 'inc/table_controls_htmx.html' with table_modal="ContactTable_config" %}
+    <form method="post">
+        {% csrf_token %}
+        <div class="card">
+            <div class="card-body" id="object_list">
+                {% include 'htmx/table.html' %}
+            </div>
+        </div>
+    </form>
+{% endblock content %}
+
+{% block modals %}
+    {{ block.super }}
+    {% table_config_form table %}
+{% endblock modals %}

+ 0 - 1
netbox/templates/tenancy/tenant.html

@@ -30,7 +30,6 @@
       {% 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">

+ 0 - 1
netbox/templates/virtualization/cluster.html

@@ -84,7 +84,6 @@
   </div>
     {% include 'inc/panels/custom_fields.html' %}
     {% include 'inc/panels/tags.html' %}
-    {% include 'inc/panels/contacts.html' %}
     {% plugin_right_page object %}
   </div>
 </div>

+ 0 - 1
netbox/templates/virtualization/clustergroup.html

@@ -37,7 +37,6 @@
 	<div class="col col-md-6">
     {% include 'inc/panels/related_objects.html' %}
     {% include 'inc/panels/custom_fields.html' %}
-    {% include 'inc/panels/contacts.html' %}
     {% plugin_right_page object %}
   </div>
 </div>

+ 0 - 1
netbox/templates/virtualization/virtualmachine.html

@@ -158,7 +158,6 @@
             </div>
           {% endif %}
         </div>
-        {% include 'inc/panels/contacts.html' %}
         {% plugin_right_page object %}
     </div>
 </div>

+ 30 - 2
netbox/tenancy/views.py

@@ -7,17 +7,40 @@ from dcim.models import Cable, Device, Location, Rack, RackReservation, Site, Vi
 from ipam.models import Aggregate, ASN, IPAddress, IPRange, L2VPN, Prefix, VLAN, VRF
 from netbox.views import generic
 from utilities.utils import count_related
-from utilities.views import register_model_view
+from utilities.views import register_model_view, ViewTab
 from virtualization.models import VirtualMachine, Cluster
 from wireless.models import WirelessLAN, WirelessLink
 from . import filtersets, forms, tables
 from .models import *
 
 
+class ObjectContactsView(generic.ObjectChildrenView):
+    child_model = Contact
+    table = tables.ContactTable
+    filterset = filtersets.ContactFilterSet
+    template_name = 'tenancy/object_contacts.html'
+    tab = ViewTab(
+        label=_('Contacts'),
+        badge=lambda obj: obj.contacts.count(),
+        permission='tenancy.view_contact',
+        weight=5000
+    )
+
+    def get_children(self, request, parent):
+        return Contact.objects.annotate(
+            assignment_count=count_related(ContactAssignment, 'contact')
+        ).restrict(request.user, 'view').filter(assignments__object_id=parent.pk)
+
+    def get_extra_context(self, request, instance):
+        return {
+            'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html',
+        }
+
 #
 # Tenant groups
 #
 
+
 class TenantGroupListView(generic.ObjectListView):
     queryset = TenantGroup.objects.add_related_count(
         TenantGroup.objects.all(),
@@ -165,6 +188,11 @@ class TenantBulkDeleteView(generic.BulkDeleteView):
     table = tables.TenantTable
 
 
+@register_model_view(Tenant, 'contacts')
+class TenantContactsView(ObjectContactsView):
+    queryset = Tenant.objects.all()
+
+
 #
 # Contact groups
 #
@@ -342,11 +370,11 @@ class ContactBulkDeleteView(generic.BulkDeleteView):
     filterset = filtersets.ContactFilterSet
     table = tables.ContactTable
 
-
 #
 # Contact assignments
 #
 
+
 class ContactAssignmentListView(generic.ObjectListView):
     queryset = ContactAssignment.objects.all()
     filterset = filtersets.ContactAssignmentFilterSet

+ 17 - 1
netbox/virtualization/views.py

@@ -9,9 +9,10 @@ from dcim.filtersets import DeviceFilterSet
 from dcim.models import Device
 from dcim.tables import DeviceTable
 from extras.views import ObjectConfigContextView
-from ipam.models import IPAddress, Service
+from ipam.models import IPAddress
 from ipam.tables import InterfaceVLANTable
 from netbox.views import generic
+from tenancy.views import ObjectContactsView
 from utilities.utils import count_related
 from utilities.views import ViewTab, register_model_view
 from . import filtersets, forms, tables
@@ -140,6 +141,11 @@ class ClusterGroupBulkDeleteView(generic.BulkDeleteView):
     table = tables.ClusterGroupTable
 
 
+@register_model_view(ClusterGroup, 'contacts')
+class ClusterGroupContactsView(ObjectContactsView):
+    queryset = ClusterGroup.objects.all()
+
+
 #
 # Clusters
 #
@@ -312,6 +318,11 @@ class ClusterRemoveDevicesView(generic.ObjectEditView):
         })
 
 
+@register_model_view(Cluster, 'contacts')
+class ClusterContactsView(ObjectContactsView):
+    queryset = Cluster.objects.all()
+
+
 #
 # Virtual machines
 #
@@ -390,6 +401,11 @@ class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
     table = tables.VirtualMachineTable
 
 
+@register_model_view(VirtualMachine, 'contacts')
+class VirtualMachineContactsView(ObjectContactsView):
+    queryset = VirtualMachine.objects.all()
+
+
 #
 # VM interfaces
 #