Fixes: #18305 make contacts mixin available for plugins (#19029)

This commit is contained in:
Renato Almeida de Oliveira 2025-04-01 10:03:25 -03:00 committed by GitHub
parent 1508e3a770
commit 864db469ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 38 additions and 131 deletions

View File

@ -117,6 +117,8 @@ For more information about database migrations, see the [Django documentation](h
::: netbox.models.features.CloningMixin ::: netbox.models.features.CloningMixin
::: netbox.models.features.ContactsMixin
::: netbox.models.features.CustomLinksMixin ::: netbox.models.features.CustomLinksMixin
::: netbox.models.features.CustomFieldsMixin ::: netbox.models.features.CustomFieldsMixin

View File

@ -5,7 +5,6 @@ from django.utils.translation import gettext_lazy as _
from dcim.views import PathTraceView from dcim.views import PathTraceView
from netbox.views import generic from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from utilities.query import count_related from utilities.query import count_related
from utilities.views import GetRelatedModelsMixin, register_model_view from utilities.views import GetRelatedModelsMixin, register_model_view
@ -74,11 +73,6 @@ class ProviderBulkDeleteView(generic.BulkDeleteView):
table = tables.ProviderTable table = tables.ProviderTable
@register_model_view(Provider, 'contacts')
class ProviderContactsView(ObjectContactsView):
queryset = Provider.objects.all()
# #
# ProviderAccounts # ProviderAccounts
# #
@ -141,11 +135,6 @@ class ProviderAccountBulkDeleteView(generic.BulkDeleteView):
table = tables.ProviderAccountTable table = tables.ProviderAccountTable
@register_model_view(ProviderAccount, 'contacts')
class ProviderAccountContactsView(ObjectContactsView):
queryset = ProviderAccount.objects.all()
# #
# Provider networks # Provider networks
# #
@ -413,11 +402,6 @@ class CircuitSwapTerminations(generic.ObjectEditView):
}) })
@register_model_view(Circuit, 'contacts')
class CircuitContactsView(ObjectContactsView):
queryset = Circuit.objects.all()
# #
# Circuit terminations # Circuit terminations
# #

View File

@ -19,7 +19,6 @@ from ipam.models import ASN, IPAddress, Prefix, VLANGroup
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
from netbox.constants import DEFAULT_ACTION_PERMISSIONS from netbox.constants import DEFAULT_ACTION_PERMISSIONS
from netbox.views import generic from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.permissions import get_permission_for_model from utilities.permissions import get_permission_for_model
@ -304,11 +303,6 @@ class RegionBulkDeleteView(generic.BulkDeleteView):
table = tables.RegionTable table = tables.RegionTable
@register_model_view(Region, 'contacts')
class RegionContactsView(ObjectContactsView):
queryset = Region.objects.all()
# #
# Site groups # Site groups
# #
@ -412,11 +406,6 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView):
table = tables.SiteGroupTable table = tables.SiteGroupTable
@register_model_view(SiteGroup, 'contacts')
class SiteGroupContactsView(ObjectContactsView):
queryset = SiteGroup.objects.all()
# #
# Sites # Sites
# #
@ -494,11 +483,6 @@ class SiteBulkDeleteView(generic.BulkDeleteView):
table = tables.SiteTable table = tables.SiteTable
@register_model_view(Site, 'contacts')
class SiteContactsView(ObjectContactsView):
queryset = Site.objects.all()
# #
# Locations # Locations
# #
@ -596,11 +580,6 @@ class LocationBulkDeleteView(generic.BulkDeleteView):
table = tables.LocationTable table = tables.LocationTable
@register_model_view(Location, 'contacts')
class LocationContactsView(ObjectContactsView):
queryset = Location.objects.all()
# #
# Rack roles # Rack roles
# #
@ -887,11 +866,6 @@ class RackBulkDeleteView(generic.BulkDeleteView):
table = tables.RackTable table = tables.RackTable
@register_model_view(Rack, 'contacts')
class RackContactsView(ObjectContactsView):
queryset = Rack.objects.all()
# #
# Rack reservations # Rack reservations
# #
@ -1029,11 +1003,6 @@ class ManufacturerBulkDeleteView(generic.BulkDeleteView):
table = tables.ManufacturerTable table = tables.ManufacturerTable
@register_model_view(Manufacturer, 'contacts')
class ManufacturerContactsView(ObjectContactsView):
queryset = Manufacturer.objects.all()
# #
# Device types # Device types
# #
@ -2360,11 +2329,6 @@ class DeviceBulkRenameView(generic.BulkRenameView):
table = tables.DeviceTable table = tables.DeviceTable
@register_model_view(Device, 'contacts')
class DeviceContactsView(ObjectContactsView):
queryset = Device.objects.all()
# #
# Modules # Modules
# #
@ -3924,11 +3888,6 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView):
table = tables.PowerPanelTable table = tables.PowerPanelTable
@register_model_view(PowerPanel, 'contacts')
class PowerPanelContactsView(ObjectContactsView):
queryset = PowerPanel.objects.all()
# #
# Power feeds # Power feeds
# #

View File

@ -11,7 +11,6 @@ from dcim.forms import InterfaceFilterForm
from dcim.models import Interface, Site from dcim.models import Interface, Site
from ipam.tables import VLANTranslationRuleTable from ipam.tables import VLANTranslationRuleTable
from netbox.views import generic from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.query import count_related from utilities.query import count_related
from utilities.tables import get_table_ordering from utilities.tables import get_table_ordering
from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view
@ -434,11 +433,6 @@ class AggregateBulkDeleteView(generic.BulkDeleteView):
table = tables.AggregateTable table = tables.AggregateTable
@register_model_view(Aggregate, 'contacts')
class AggregateContactsView(ObjectContactsView):
queryset = Aggregate.objects.all()
# #
# Prefix/VLAN roles # Prefix/VLAN roles
# #
@ -684,11 +678,6 @@ class PrefixBulkDeleteView(generic.BulkDeleteView):
table = tables.PrefixTable table = tables.PrefixTable
@register_model_view(Prefix, 'contacts')
class PrefixContactsView(ObjectContactsView):
queryset = Prefix.objects.all()
# #
# IP Ranges # IP Ranges
# #
@ -778,11 +767,6 @@ class IPRangeBulkDeleteView(generic.BulkDeleteView):
table = tables.IPRangeTable table = tables.IPRangeTable
@register_model_view(IPRange, 'contacts')
class IPRangeContactsView(ObjectContactsView):
queryset = IPRange.objects.all()
# #
# IP addresses # IP addresses
# #
@ -964,11 +948,6 @@ class IPAddressRelatedIPsView(generic.ObjectChildrenView):
return parent.get_related_ips().restrict(request.user, 'view') return parent.get_related_ips().restrict(request.user, 'view')
@register_model_view(IPAddress, 'contacts')
class IPAddressContactsView(ObjectContactsView):
queryset = IPAddress.objects.all()
# #
# VLAN groups # VLAN groups
# #
@ -1476,8 +1455,3 @@ class ServiceBulkDeleteView(generic.BulkDeleteView):
queryset = Service.objects.prefetch_related('device', 'virtual_machine') queryset = Service.objects.prefetch_related('device', 'virtual_machine')
filterset = filtersets.ServiceFilterSet filterset = filtersets.ServiceFilterSet
table = tables.ServiceTable table = tables.ServiceTable
@register_model_view(Service, 'contacts')
class ServiceContactsView(ObjectContactsView):
queryset = Service.objects.all()

View File

@ -353,7 +353,7 @@ class ImageAttachmentsMixin(models.Model):
class ContactsMixin(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( contacts = GenericRelation(
to='tenancy.ContactAssignment', to='tenancy.ContactAssignment',
@ -368,7 +368,8 @@ class ContactsMixin(models.Model):
""" """
Return a `QuerySet` matching all contacts assigned to this object. 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 tenancy.models import ContactAssignment
from . import NestedGroupModel from . import NestedGroupModel
@ -659,6 +660,10 @@ def register_models(*models):
) )
# Register applicable feature views for the model # 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): if issubclass(model, JournalingMixin):
register_model_view(model, 'journal', kwargs={'model': model})( register_model_view(model, 'journal', kwargs={'model': model})(
'netbox.views.generic.ObjectJournalView' 'netbox.views.generic.ObjectJournalView'

View File

@ -12,13 +12,19 @@ from core.tables import JobTable, ObjectChangeTable
from extras.forms import JournalEntryForm from extras.forms import JournalEntryForm
from extras.models import JournalEntry from extras.models import JournalEntry
from extras.tables import JournalEntryTable 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.permissions import get_permission_for_model
from utilities.views import ConditionalLoginRequiredMixin, GetReturnURLMixin, ViewTab from utilities.views import ConditionalLoginRequiredMixin, GetReturnURLMixin, ViewTab
from .base import BaseMultiObjectView from .base import BaseMultiObjectView
from .object_views import ObjectChildrenView
__all__ = ( __all__ = (
'BulkSyncDataView', 'BulkSyncDataView',
'ObjectChangeLogView', 'ObjectChangeLogView',
'ObjectContactsView',
'ObjectJobsView', 'ObjectJobsView',
'ObjectJournalView', 'ObjectJournalView',
'ObjectSyncDataView', 'ObjectSyncDataView',
@ -244,3 +250,25 @@ class BulkSyncDataView(GetReturnURLMixin, BaseMultiObjectView):
)) ))
return redirect(self.get_return_url(request)) 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')

View File

@ -1,31 +1,13 @@
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from netbox.views import generic from netbox.views import generic
from utilities.query import count_related 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 . import filtersets, forms, tables
from .models import * 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 # Tenant groups
# #
@ -156,11 +138,6 @@ class TenantBulkDeleteView(generic.BulkDeleteView):
table = tables.TenantTable table = tables.TenantTable
@register_model_view(Tenant, 'contacts')
class TenantContactsView(ObjectContactsView):
queryset = Tenant.objects.all()
# #
# Contact groups # Contact groups
# #

View File

@ -16,7 +16,6 @@ from ipam.models import IPAddress
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
from netbox.constants import DEFAULT_ACTION_PERMISSIONS from netbox.constants import DEFAULT_ACTION_PERMISSIONS
from netbox.views import generic from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.query import count_related from utilities.query import count_related
from utilities.query_functions import CollateAsChar from utilities.query_functions import CollateAsChar
from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view
@ -148,11 +147,6 @@ class ClusterGroupBulkDeleteView(generic.BulkDeleteView):
table = tables.ClusterGroupTable table = tables.ClusterGroupTable
@register_model_view(ClusterGroup, 'contacts')
class ClusterGroupContactsView(ObjectContactsView):
queryset = ClusterGroup.objects.all()
# #
# Clusters # Clusters
# #
@ -344,11 +338,6 @@ class ClusterRemoveDevicesView(generic.ObjectEditView):
}) })
@register_model_view(Cluster, 'contacts')
class ClusterContactsView(ObjectContactsView):
queryset = Cluster.objects.all()
# #
# Virtual machines # Virtual machines
# #
@ -509,11 +498,6 @@ class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
table = tables.VirtualMachineTable table = tables.VirtualMachineTable
@register_model_view(VirtualMachine, 'contacts')
class VirtualMachineContactsView(ObjectContactsView):
queryset = VirtualMachine.objects.all()
# #
# VM interfaces # VM interfaces
# #

View File

@ -1,6 +1,5 @@
from ipam.tables import RouteTargetTable from ipam.tables import RouteTargetTable
from netbox.views import generic from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.query import count_related from utilities.query import count_related
from utilities.views import GetRelatedModelsMixin, register_model_view from utilities.views import GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables from . import filtersets, forms, tables
@ -497,11 +496,6 @@ class L2VPNBulkDeleteView(generic.BulkDeleteView):
table = tables.L2VPNTable table = tables.L2VPNTable
@register_model_view(L2VPN, 'contacts')
class L2VPNContactsView(ObjectContactsView):
queryset = L2VPN.objects.all()
# #
# L2VPN terminations # L2VPN terminations
# #