From daa8f71bb626d35852633860975f9d7871e7035c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 25 Jul 2023 15:28:19 -0400 Subject: [PATCH] Closes #10197: Add a cached counter field for virtual chassis members --- netbox/dcim/api/serializers.py | 4 ++- netbox/dcim/api/views.py | 4 +-- netbox/dcim/apps.py | 4 +-- .../0177_virtual_chassis_member_counter.py | 35 +++++++++++++++++++ netbox/dcim/models/devices.py | 9 ++++- netbox/dcim/views.py | 4 +-- netbox/templates/dcim/virtualchassis.html | 10 ++++++ 7 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 netbox/dcim/migrations/0177_virtual_chassis_member_counter.py diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index edcb64019..bde32c871 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -1156,13 +1156,15 @@ class CablePathSerializer(serializers.ModelSerializer): class VirtualChassisSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail') master = NestedDeviceSerializer(required=False, allow_null=True, default=None) + + # Counter fields member_count = serializers.IntegerField(read_only=True) class Meta: model = VirtualChassis fields = [ 'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields', - 'member_count', 'created', 'last_updated', + 'created', 'last_updated', 'member_count', ] diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index e8a2eabbf..dfedc7432 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -579,9 +579,7 @@ class CableTerminationViewSet(NetBoxModelViewSet): # class VirtualChassisViewSet(NetBoxModelViewSet): - queryset = VirtualChassis.objects.prefetch_related('tags').annotate( - member_count=count_related(Device, 'virtual_chassis') - ) + queryset = VirtualChassis.objects.prefetch_related('tags') serializer_class = serializers.VirtualChassisSerializer filterset_class = filtersets.VirtualChassisFilterSet brief_prefetch_fields = ['master'] diff --git a/netbox/dcim/apps.py b/netbox/dcim/apps.py index cc4c65f93..38e9b9c6b 100644 --- a/netbox/dcim/apps.py +++ b/netbox/dcim/apps.py @@ -9,7 +9,7 @@ class DCIMConfig(AppConfig): def ready(self): from . import signals, search - from .models import CableTermination, Device + from .models import CableTermination, Device, VirtualChassis from utilities.counters import connect_counters # Register denormalized fields @@ -27,4 +27,4 @@ class DCIMConfig(AppConfig): }) # Register counters - connect_counters(Device) + connect_counters(Device, VirtualChassis) diff --git a/netbox/dcim/migrations/0177_virtual_chassis_member_counter.py b/netbox/dcim/migrations/0177_virtual_chassis_member_counter.py new file mode 100644 index 000000000..2c6863f5c --- /dev/null +++ b/netbox/dcim/migrations/0177_virtual_chassis_member_counter.py @@ -0,0 +1,35 @@ +from django.db import migrations +from django.db.models import Count + +import utilities.fields + + +def populate_virtualchassis_members(apps, schema_editor): + VirtualChassis = apps.get_model('dcim', 'VirtualChassis') + + vcs = list(VirtualChassis.objects.annotate(_member_count=Count('members', distinct=True))) + + for vc in vcs: + vc.member_count = vc._member_count + + VirtualChassis.objects.bulk_update(vcs, ['member_count']) + + +class Migration(migrations.Migration): + dependencies = [ + ('dcim', '0176_device_component_counters'), + ] + + operations = [ + migrations.AddField( + model_name='virtualchassis', + name='member_count', + field=utilities.fields.CounterCacheField( + default=0, to_field='virtual_chassis', to_model='dcim.Device' + ), + ), + migrations.RunPython( + code=populate_virtualchassis_members, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 76100197b..6eed6b09d 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -22,6 +22,7 @@ from netbox.config import ConfigItem from netbox.models import OrganizationalModel, PrimaryModel from utilities.choices import ColorChoices from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField +from utilities.tracking import TrackingModelMixin from .device_components import * from .mixins import WeightMixin @@ -469,7 +470,7 @@ def update_interface_bridges(device, interface_templates, module=None): interface.save() -class Device(PrimaryModel, ConfigContextModel): +class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin): """ A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType, DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique. @@ -1206,6 +1207,12 @@ class VirtualChassis(PrimaryModel): blank=True ) + # Counter fields + member_count = CounterCacheField( + to_model='dcim.Device', + to_field='virtual_chassis' + ) + class Meta: ordering = ['name'] verbose_name_plural = 'virtual chassis' diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 1d46de231..9c6fd6b44 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -3227,9 +3227,7 @@ class InterfaceConnectionsListView(generic.ObjectListView): # class VirtualChassisListView(generic.ObjectListView): - queryset = VirtualChassis.objects.annotate( - member_count=count_related(Device, 'virtual_chassis') - ) + queryset = VirtualChassis.objects.all() table = tables.VirtualChassisTable filterset = filtersets.VirtualChassisFilterSet filterset_form = forms.VirtualChassisFilterForm diff --git a/netbox/templates/dcim/virtualchassis.html b/netbox/templates/dcim/virtualchassis.html index d0fba3ca2..62b4f3dc2 100644 --- a/netbox/templates/dcim/virtualchassis.html +++ b/netbox/templates/dcim/virtualchassis.html @@ -31,6 +31,16 @@ Description {{ object.description|placeholder }} + + Members + + {% if object.member_count %} + {{ object.member_count }} + {% else %} + {{ object.member_count }} + {% endif %} + +