From 864db469ba31412d82eb24d3ebf537d2bdacac9f Mon Sep 17 00:00:00 2001 From: Renato Almeida de Oliveira Date: Tue, 1 Apr 2025 10:03:25 -0300 Subject: [PATCH] Fixes: #18305 make contacts mixin available for plugins (#19029) --- docs/plugins/development/models.md | 2 + netbox/circuits/views.py | 16 -------- netbox/dcim/views.py | 41 -------------------- netbox/ipam/views.py | 26 ------------- netbox/netbox/models/features.py | 9 ++++- netbox/netbox/views/generic/feature_views.py | 28 +++++++++++++ netbox/tenancy/views.py | 25 +----------- netbox/virtualization/views.py | 16 -------- netbox/vpn/views.py | 6 --- 9 files changed, 38 insertions(+), 131 deletions(-) diff --git a/docs/plugins/development/models.md b/docs/plugins/development/models.md index 03cedda16..492b7fc97 100644 --- a/docs/plugins/development/models.md +++ b/docs/plugins/development/models.md @@ -117,6 +117,8 @@ For more information about database migrations, see the [Django documentation](h ::: netbox.models.features.CloningMixin +::: netbox.models.features.ContactsMixin + ::: netbox.models.features.CustomLinksMixin ::: netbox.models.features.CustomFieldsMixin diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 3bd81c33a..644251c35 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -5,7 +5,6 @@ from django.utils.translation import gettext_lazy as _ from dcim.views import PathTraceView from netbox.views import generic -from tenancy.views import ObjectContactsView from utilities.forms import ConfirmationForm from utilities.query import count_related from utilities.views import GetRelatedModelsMixin, register_model_view @@ -74,11 +73,6 @@ class ProviderBulkDeleteView(generic.BulkDeleteView): table = tables.ProviderTable -@register_model_view(Provider, 'contacts') -class ProviderContactsView(ObjectContactsView): - queryset = Provider.objects.all() - - # # ProviderAccounts # @@ -141,11 +135,6 @@ class ProviderAccountBulkDeleteView(generic.BulkDeleteView): table = tables.ProviderAccountTable -@register_model_view(ProviderAccount, 'contacts') -class ProviderAccountContactsView(ObjectContactsView): - queryset = ProviderAccount.objects.all() - - # # Provider networks # @@ -413,11 +402,6 @@ class CircuitSwapTerminations(generic.ObjectEditView): }) -@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 0978747d1..4b2f22035 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -19,7 +19,6 @@ from ipam.models import ASN, IPAddress, Prefix, VLANGroup from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable from netbox.constants import DEFAULT_ACTION_PERMISSIONS 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 @@ -304,11 +303,6 @@ class RegionBulkDeleteView(generic.BulkDeleteView): table = tables.RegionTable -@register_model_view(Region, 'contacts') -class RegionContactsView(ObjectContactsView): - queryset = Region.objects.all() - - # # Site groups # @@ -412,11 +406,6 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView): table = tables.SiteGroupTable -@register_model_view(SiteGroup, 'contacts') -class SiteGroupContactsView(ObjectContactsView): - queryset = SiteGroup.objects.all() - - # # Sites # @@ -494,11 +483,6 @@ class SiteBulkDeleteView(generic.BulkDeleteView): table = tables.SiteTable -@register_model_view(Site, 'contacts') -class SiteContactsView(ObjectContactsView): - queryset = Site.objects.all() - - # # Locations # @@ -596,11 +580,6 @@ class LocationBulkDeleteView(generic.BulkDeleteView): table = tables.LocationTable -@register_model_view(Location, 'contacts') -class LocationContactsView(ObjectContactsView): - queryset = Location.objects.all() - - # # Rack roles # @@ -887,11 +866,6 @@ class RackBulkDeleteView(generic.BulkDeleteView): table = tables.RackTable -@register_model_view(Rack, 'contacts') -class RackContactsView(ObjectContactsView): - queryset = Rack.objects.all() - - # # Rack reservations # @@ -1029,11 +1003,6 @@ class ManufacturerBulkDeleteView(generic.BulkDeleteView): table = tables.ManufacturerTable -@register_model_view(Manufacturer, 'contacts') -class ManufacturerContactsView(ObjectContactsView): - queryset = Manufacturer.objects.all() - - # # Device types # @@ -2360,11 +2329,6 @@ class DeviceBulkRenameView(generic.BulkRenameView): table = tables.DeviceTable -@register_model_view(Device, 'contacts') -class DeviceContactsView(ObjectContactsView): - queryset = Device.objects.all() - - # # Modules # @@ -3924,11 +3888,6 @@ 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 007a652ca..3dde80b30 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -11,7 +11,6 @@ from dcim.forms import InterfaceFilterForm from dcim.models import Interface, Site from ipam.tables import VLANTranslationRuleTable from netbox.views import generic -from tenancy.views import ObjectContactsView from utilities.query import count_related from utilities.tables import get_table_ordering from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view @@ -434,11 +433,6 @@ class AggregateBulkDeleteView(generic.BulkDeleteView): table = tables.AggregateTable -@register_model_view(Aggregate, 'contacts') -class AggregateContactsView(ObjectContactsView): - queryset = Aggregate.objects.all() - - # # Prefix/VLAN roles # @@ -684,11 +678,6 @@ class PrefixBulkDeleteView(generic.BulkDeleteView): table = tables.PrefixTable -@register_model_view(Prefix, 'contacts') -class PrefixContactsView(ObjectContactsView): - queryset = Prefix.objects.all() - - # # IP Ranges # @@ -778,11 +767,6 @@ class IPRangeBulkDeleteView(generic.BulkDeleteView): table = tables.IPRangeTable -@register_model_view(IPRange, 'contacts') -class IPRangeContactsView(ObjectContactsView): - queryset = IPRange.objects.all() - - # # IP addresses # @@ -964,11 +948,6 @@ class IPAddressRelatedIPsView(generic.ObjectChildrenView): return parent.get_related_ips().restrict(request.user, 'view') -@register_model_view(IPAddress, 'contacts') -class IPAddressContactsView(ObjectContactsView): - queryset = IPAddress.objects.all() - - # # VLAN groups # @@ -1476,8 +1455,3 @@ class ServiceBulkDeleteView(generic.BulkDeleteView): queryset = Service.objects.prefetch_related('device', 'virtual_machine') filterset = filtersets.ServiceFilterSet table = tables.ServiceTable - - -@register_model_view(Service, 'contacts') -class ServiceContactsView(ObjectContactsView): - queryset = Service.objects.all() diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index a2fb8d615..d14fdb17f 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -353,7 +353,7 @@ class ImageAttachmentsMixin(models.Model): class ContactsMixin(models.Model): """ - Enables the assignments of Contacts (via ContactAssignment). + Enables the assignment of Contacts to a model (via ContactAssignment). """ contacts = GenericRelation( to='tenancy.ContactAssignment', @@ -368,7 +368,8 @@ class ContactsMixin(models.Model): """ Return a `QuerySet` matching all contacts assigned to this object. - :param inherited: If `True`, inherited contacts from parent objects are included. + Args: + inherited: If `True`, inherited contacts from parent objects are included. """ from tenancy.models import ContactAssignment from . import NestedGroupModel @@ -659,6 +660,10 @@ def register_models(*models): ) # Register applicable feature views for the model + if issubclass(model, ContactsMixin): + register_model_view(model, 'contacts', kwargs={'model': model})( + 'netbox.views.generic.ObjectContactsView' + ) if issubclass(model, JournalingMixin): register_model_view(model, 'journal', kwargs={'model': model})( 'netbox.views.generic.ObjectJournalView' diff --git a/netbox/netbox/views/generic/feature_views.py b/netbox/netbox/views/generic/feature_views.py index 1e17d5354..63bbc86a5 100644 --- a/netbox/netbox/views/generic/feature_views.py +++ b/netbox/netbox/views/generic/feature_views.py @@ -12,13 +12,19 @@ from core.tables import JobTable, ObjectChangeTable from extras.forms import JournalEntryForm from extras.models import JournalEntry from extras.tables import JournalEntryTable +from tenancy.models import ContactAssignment +from tenancy.tables import ContactAssignmentTable +from tenancy.filtersets import ContactAssignmentFilterSet +from tenancy.forms import ContactAssignmentFilterForm from utilities.permissions import get_permission_for_model from utilities.views import ConditionalLoginRequiredMixin, GetReturnURLMixin, ViewTab from .base import BaseMultiObjectView +from .object_views import ObjectChildrenView __all__ = ( 'BulkSyncDataView', 'ObjectChangeLogView', + 'ObjectContactsView', 'ObjectJobsView', 'ObjectJournalView', 'ObjectSyncDataView', @@ -244,3 +250,25 @@ class BulkSyncDataView(GetReturnURLMixin, BaseMultiObjectView): )) return redirect(self.get_return_url(request)) + + +class ObjectContactsView(ObjectChildrenView): + child_model = ContactAssignment + table = ContactAssignmentTable + filterset = ContactAssignmentFilterSet + filterset_form = ContactAssignmentFilterForm + template_name = 'tenancy/object_contacts.html' + tab = ViewTab( + label=_('Contacts'), + badge=lambda obj: obj.get_contacts().count(), + permission='tenancy.view_contactassignment', + weight=5000 + ) + + def dispatch(self, request, *args, **kwargs): + model = kwargs.pop('model') + self.queryset = model.objects.all() + return super().dispatch(request, *args, **kwargs) + + def get_children(self, request, parent): + return parent.get_contacts().restrict(request.user, 'view').order_by('priority', 'contact', 'role') diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index d0c80b76f..dd584d745 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -1,31 +1,13 @@ from django.contrib.contenttypes.models import ContentType from django.shortcuts import get_object_or_404 -from django.utils.translation import gettext_lazy as _ from netbox.views import generic from utilities.query import count_related -from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view +from utilities.views import GetRelatedModelsMixin, register_model_view from . import filtersets, forms, tables from .models import * -class ObjectContactsView(generic.ObjectChildrenView): - child_model = ContactAssignment - table = tables.ContactAssignmentTable - filterset = filtersets.ContactAssignmentFilterSet - filterset_form = forms.ContactAssignmentFilterForm - template_name = 'tenancy/object_contacts.html' - tab = ViewTab( - label=_('Contacts'), - badge=lambda obj: obj.get_contacts().count(), - permission='tenancy.view_contactassignment', - weight=5000 - ) - - def get_children(self, request, parent): - return parent.get_contacts().restrict(request.user, 'view').order_by('priority', 'contact', 'role') - - # # Tenant groups # @@ -156,11 +138,6 @@ class TenantBulkDeleteView(generic.BulkDeleteView): table = tables.TenantTable -@register_model_view(Tenant, 'contacts') -class TenantContactsView(ObjectContactsView): - queryset = Tenant.objects.all() - - # # Contact groups # diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 7682d0fc8..e76f2d52f 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -16,7 +16,6 @@ from ipam.models import IPAddress from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable from netbox.constants import DEFAULT_ACTION_PERMISSIONS from netbox.views import generic -from tenancy.views import ObjectContactsView from utilities.query import count_related from utilities.query_functions import CollateAsChar from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view @@ -148,11 +147,6 @@ class ClusterGroupBulkDeleteView(generic.BulkDeleteView): table = tables.ClusterGroupTable -@register_model_view(ClusterGroup, 'contacts') -class ClusterGroupContactsView(ObjectContactsView): - queryset = ClusterGroup.objects.all() - - # # Clusters # @@ -344,11 +338,6 @@ class ClusterRemoveDevicesView(generic.ObjectEditView): }) -@register_model_view(Cluster, 'contacts') -class ClusterContactsView(ObjectContactsView): - queryset = Cluster.objects.all() - - # # Virtual machines # @@ -509,11 +498,6 @@ class VirtualMachineBulkDeleteView(generic.BulkDeleteView): table = tables.VirtualMachineTable -@register_model_view(VirtualMachine, 'contacts') -class VirtualMachineContactsView(ObjectContactsView): - queryset = VirtualMachine.objects.all() - - # # VM interfaces # diff --git a/netbox/vpn/views.py b/netbox/vpn/views.py index 3372e9412..8206f4541 100644 --- a/netbox/vpn/views.py +++ b/netbox/vpn/views.py @@ -1,6 +1,5 @@ from ipam.tables import RouteTargetTable from netbox.views import generic -from tenancy.views import ObjectContactsView from utilities.query import count_related from utilities.views import GetRelatedModelsMixin, register_model_view from . import filtersets, forms, tables @@ -497,11 +496,6 @@ class L2VPNBulkDeleteView(generic.BulkDeleteView): table = tables.L2VPNTable -@register_model_view(L2VPN, 'contacts') -class L2VPNContactsView(ObjectContactsView): - queryset = L2VPN.objects.all() - - # # L2VPN terminations #