From 701f40e2a8e46ebcf8bf11707f1c418bbc06c9c8 Mon Sep 17 00:00:00 2001 From: Alexander Haase Date: Sun, 16 Feb 2025 20:04:12 +0100 Subject: [PATCH 1/5] Show parent contacts for nested models When contacts of a nested model are displayed, the contacts of the parents are also displayed. --- netbox/tenancy/views.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 0988d2e6542..9bb542f8250 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -2,6 +2,7 @@ from django.shortcuts import get_object_or_404 from django.utils.translation import gettext_lazy as _ +from netbox.models import NestedGroupModel from netbox.views import generic from utilities.query import count_related from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view @@ -23,19 +24,18 @@ class ObjectContactsView(generic.ObjectChildrenView): ) def get_children(self, request, parent): - return ContactAssignment.objects.restrict(request.user, 'view').filter( - object_type=ContentType.objects.get_for_model(parent), - object_id=parent.pk - ).order_by('priority', 'contact', 'role') - - def get_table(self, *args, **kwargs): - table = super().get_table(*args, **kwargs) - - # Hide object columns - table.columns.hide('object_type') - table.columns.hide('object') - - return table + qs = ContactAssignment.objects.restrict(request.user, 'view') + for obj in [parent]: + qs = qs.filter( + object_type=ContentType.objects.get_for_model(obj), + object_id__in=( + obj.get_ancestors(include_self=True).values_list('pk', flat=True) + if isinstance(obj, NestedGroupModel) + else [obj.pk] + ), + ) + + return qs.order_by('priority', 'contact', 'role') # From d5316de9c84daf258b43af002e1f156ac41c18cb Mon Sep 17 00:00:00 2001 From: Alexander Haase Date: Tue, 18 Feb 2025 23:02:57 +0100 Subject: [PATCH 2/5] Move contact queryset into model --- netbox/netbox/models/features.py | 21 +++++++++++++++++++++ netbox/tenancy/views.py | 16 ++-------------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index a972277705c..70027a9fc7d 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -5,6 +5,7 @@ from django.contrib.contenttypes.fields import GenericRelation from django.core.validators import ValidationError from django.db import models +from django.db.models import Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ from taggit.managers import TaggableManager @@ -363,6 +364,26 @@ class ContactsMixin(models.Model): class Meta: abstract = True + def get_contacts(self): + """ + Return a `QuerySet` matching all contacts assigned to this object. + """ + from tenancy.models import ContactAssignment + from . import NestedGroupModel + + filter = Q() + for obj in [self]: + filter |= Q( + object_type=ObjectType.objects.get_for_model(obj), + object_id__in=( + obj.get_ancestors(include_self=True).values_list('pk', flat=True) + if isinstance(obj, NestedGroupModel) + else [obj.pk] + ), + ) + + return ContactAssignment.objects.filter(filter) + class BookmarksMixin(models.Model): """ diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 9bb542f8250..3b5029bd793 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -2,7 +2,6 @@ from django.shortcuts import get_object_or_404 from django.utils.translation import gettext_lazy as _ -from netbox.models import NestedGroupModel from netbox.views import generic from utilities.query import count_related from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view @@ -18,24 +17,13 @@ class ObjectContactsView(generic.ObjectChildrenView): template_name = 'tenancy/object_contacts.html' tab = ViewTab( label=_('Contacts'), - badge=lambda obj: obj.contacts.count(), + badge=lambda obj: obj.get_contacts().count(), permission='tenancy.view_contactassignment', weight=5000 ) def get_children(self, request, parent): - qs = ContactAssignment.objects.restrict(request.user, 'view') - for obj in [parent]: - qs = qs.filter( - object_type=ContentType.objects.get_for_model(obj), - object_id__in=( - obj.get_ancestors(include_self=True).values_list('pk', flat=True) - if isinstance(obj, NestedGroupModel) - else [obj.pk] - ), - ) - - return qs.order_by('priority', 'contact', 'role') + return parent.get_contacts().restrict(request.user, 'view').order_by('priority', 'contact', 'role') # From 72adda11974f81fbc0cc9edfc74654009c74b1b9 Mon Sep 17 00:00:00 2001 From: Alexander Haase Date: Tue, 18 Feb 2025 23:08:47 +0100 Subject: [PATCH 3/5] Allow exclusion of inherited contacts --- netbox/netbox/models/features.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 70027a9fc7d..ba895d5ed03 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -364,9 +364,11 @@ class ContactsMixin(models.Model): class Meta: abstract = True - def get_contacts(self): + def get_contacts(self, inherited=True): """ Return a `QuerySet` matching all contacts assigned to this object. + + :param inherited: If `True`, inherited contacts from parent objects are included. """ from tenancy.models import ContactAssignment from . import NestedGroupModel @@ -377,7 +379,7 @@ def get_contacts(self): object_type=ObjectType.objects.get_for_model(obj), object_id__in=( obj.get_ancestors(include_self=True).values_list('pk', flat=True) - if isinstance(obj, NestedGroupModel) + if (isinstance(obj, NestedGroupModel) and inherited) else [obj.pk] ), ) From ca6b686b88ac68550712adf74b4f68cbcac3f8ee Mon Sep 17 00:00:00 2001 From: Alexander Haase Date: Sat, 22 Feb 2025 00:06:44 +0100 Subject: [PATCH 4/5] Limit inherited contacts to model --- netbox/netbox/models/features.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index ba895d5ed03..60084c361f8 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -373,16 +373,14 @@ def get_contacts(self, inherited=True): from tenancy.models import ContactAssignment from . import NestedGroupModel - filter = Q() - for obj in [self]: - filter |= Q( - object_type=ObjectType.objects.get_for_model(obj), - object_id__in=( - obj.get_ancestors(include_self=True).values_list('pk', flat=True) - if (isinstance(obj, NestedGroupModel) and inherited) - else [obj.pk] - ), - ) + filter = Q( + object_type=ObjectType.objects.get_for_model(self), + object_id__in=( + self.get_ancestors(include_self=True).values_list('pk', flat=True) + if (isinstance(self, NestedGroupModel) and inherited) + else [self.pk] + ), + ) return ContactAssignment.objects.filter(filter) From effc23f5bbd1223398421aa99bb760a11da37be7 Mon Sep 17 00:00:00 2001 From: Alexander Haase Date: Tue, 25 Feb 2025 18:36:16 +0100 Subject: [PATCH 5/5] Optimize contact lookup query --- netbox/netbox/models/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 60084c361f8..e58037b8546 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -376,7 +376,7 @@ def get_contacts(self, inherited=True): filter = Q( object_type=ObjectType.objects.get_for_model(self), object_id__in=( - self.get_ancestors(include_self=True).values_list('pk', flat=True) + self.get_ancestors(include_self=True) if (isinstance(self, NestedGroupModel) and inherited) else [self.pk] ),