diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index e5f4faee1e0..f1cfdd1d5d3 100644 --- a/netbox/circuits/views.py +++ b/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 @@ def post(self, request, pk): }) +@register_model_view(Circuit, 'contacts') +class CircuitContactsView(ObjectContactsView): + queryset = Circuit.objects.all() + + # # Circuit terminations # diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index bcbbf17395b..0def4f4a8ae 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -20,6 +20,7 @@ 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 # diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index a49c4aab3de..f26d00bb9d4 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -9,6 +9,7 @@ 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 @@ -1291,6 +1292,11 @@ class L2VPNBulkDeleteView(generic.BulkDeleteView): table = tables.L2VPNTable +@register_model_view(L2VPN, 'contacts') +class L2VPNContactsView(ObjectContactsView): + queryset = L2VPN.objects.all() + + # # L2VPN terminations # diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index ee994e95997..a5913e2ad1a 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -70,7 +70,6 @@
Circuit
{% 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 %}
diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index 69520217640..58e62ab07cd 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -54,7 +54,6 @@
Provider
{% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/circuits/provideraccount.html b/netbox/templates/circuits/provideraccount.html index 63344ada1dd..c55663b4a09 100644 --- a/netbox/templates/circuits/provideraccount.html +++ b/netbox/templates/circuits/provideraccount.html @@ -38,7 +38,6 @@
Provider Account
{% 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 %}
diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 2c79ab006f7..df6397a38d8 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -298,7 +298,6 @@
Services
{% endif %} - {% include 'inc/panels/contacts.html' %} {% include 'inc/panels/image_attachments.html' %} {% if object.rack and object.position %}
diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html index 193d93f9a85..795aeb35f20 100644 --- a/netbox/templates/dcim/location.html +++ b/netbox/templates/dcim/location.html @@ -65,7 +65,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 %} diff --git a/netbox/templates/dcim/manufacturer.html b/netbox/templates/dcim/manufacturer.html index a60b3503c8d..8233b6fc8c6 100644 --- a/netbox/templates/dcim/manufacturer.html +++ b/netbox/templates/dcim/manufacturer.html @@ -51,7 +51,6 @@
{% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/dcim/powerpanel.html b/netbox/templates/dcim/powerpanel.html index af08f30230a..ea9210ba7ec 100644 --- a/netbox/templates/dcim/powerpanel.html +++ b/netbox/templates/dcim/powerpanel.html @@ -40,7 +40,6 @@
Power Panel
{% 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 %}
diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 9cb046b4e4b..52b5d4bfec6 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -191,7 +191,6 @@

Rear

{% include 'inc/panels/related_objects.html' %} {% include 'dcim/inc/nonracked_devices.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %} diff --git a/netbox/templates/dcim/region.html b/netbox/templates/dcim/region.html index 85587e4b5ed..05cc424d7c9 100644 --- a/netbox/templates/dcim/region.html +++ b/netbox/templates/dcim/region.html @@ -46,7 +46,6 @@
Region
{% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index d6de8f3cbda..6b802c56dd7 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -127,7 +127,6 @@
Site
{% include 'inc/panels/related_objects.html' with filter_name='site_id' %} - {% include 'inc/panels/contacts.html' %}
Locations
diff --git a/netbox/templates/dcim/sitegroup.html b/netbox/templates/dcim/sitegroup.html index 2cf8e71684a..819022a34de 100644 --- a/netbox/templates/dcim/sitegroup.html +++ b/netbox/templates/dcim/sitegroup.html @@ -42,7 +42,6 @@
Site Group
{% include 'inc/panels/tags.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_left_page object %}
diff --git a/netbox/templates/inc/panels/contacts.html b/netbox/templates/inc/panels/contacts.html deleted file mode 100644 index 359ad8d7edb..00000000000 --- a/netbox/templates/inc/panels/contacts.html +++ /dev/null @@ -1,63 +0,0 @@ -{% load helpers %} - -
-
Contacts
-
- {% with contacts=object.contacts.all %} - {% if contacts.exists %} - - - - - - - - - - {% for contact in contacts %} - - - - - - - - - {% endfor %} -
NameRolePriorityPhoneEmail
{{ contact.contact|linkify }}{{ contact.role|placeholder }}{{ contact.get_priority_display|placeholder }} - {% if contact.contact.phone %} - {{ contact.contact.phone }} - {% else %} - {{ ''|placeholder }} - {% endif %} - - {% if contact.contact.email %} - {{ contact.contact.email }} - {% else %} - {{ ''|placeholder }} - {% endif %} - - {% if perms.tenancy.change_contactassignment %} - - - - {% endif %} - {% if perms.tenancy.delete_contactassignment %} - - - - {% endif %} -
- {% else %} -
None
- {% endif %} - {% endwith %} -
- {% if perms.tenancy.add_contactassignment %} - - {% endif %} -
diff --git a/netbox/templates/ipam/l2vpn.html b/netbox/templates/ipam/l2vpn.html index 87050eb2686..8896dd6c250 100644 --- a/netbox/templates/ipam/l2vpn.html +++ b/netbox/templates/ipam/l2vpn.html @@ -37,7 +37,6 @@
L2VPN Attributes
{% plugin_left_page object %}
- {% include 'inc/panels/contacts.html' %} {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/comments.html' %} {% plugin_right_page object %} diff --git a/netbox/templates/tenancy/object_contacts.html b/netbox/templates/tenancy/object_contacts.html new file mode 100644 index 00000000000..aca63a3793c --- /dev/null +++ b/netbox/templates/tenancy/object_contacts.html @@ -0,0 +1,27 @@ +{% extends base_template %} +{% load helpers %} + +{% block extra_controls %} + {% if perms.tenancy.add_contactassignment %} + + Add a contact + + {% endif %} +{% endblock %} + +{% block content %} + {% include 'inc/table_controls_htmx.html' with table_modal="ContactTable_config" %} +
+ {% csrf_token %} +
+
+ {% include 'htmx/table.html' %} +
+
+
+{% endblock content %} + +{% block modals %} + {{ block.super }} + {% table_config_form table %} +{% endblock modals %} diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index da48f1ef590..34abe5c01b7 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -30,7 +30,6 @@
Tenant
{% 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 %}
diff --git a/netbox/templates/virtualization/cluster.html b/netbox/templates/virtualization/cluster.html index 3dfef108be3..508bca547a9 100644 --- a/netbox/templates/virtualization/cluster.html +++ b/netbox/templates/virtualization/cluster.html @@ -84,7 +84,6 @@
Allocated Resources
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/virtualization/clustergroup.html b/netbox/templates/virtualization/clustergroup.html index 5104330689b..2496ad08522 100644 --- a/netbox/templates/virtualization/clustergroup.html +++ b/netbox/templates/virtualization/clustergroup.html @@ -37,7 +37,6 @@
{% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 5098a2f8ffe..51fd8aa806e 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -158,7 +158,6 @@
Services
{% endif %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %} diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index b9ada8640d5..5f8a7e314eb 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -7,17 +7,40 @@ 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 diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 9014aa9dd3c..4a501e14e2b 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -9,9 +9,10 @@ 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 @@ def post(self, request, pk): }) +@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 #