Closes #10197: Add a cached counter field for virtual chassis members

This commit is contained in:
Jeremy Stretch 2023-07-25 15:28:19 -04:00
parent 9b6e32896d
commit daa8f71bb6
7 changed files with 60 additions and 10 deletions

View File

@ -1156,13 +1156,15 @@ class CablePathSerializer(serializers.ModelSerializer):
class VirtualChassisSerializer(NetBoxModelSerializer): class VirtualChassisSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
master = NestedDeviceSerializer(required=False, allow_null=True, default=None) master = NestedDeviceSerializer(required=False, allow_null=True, default=None)
# Counter fields
member_count = serializers.IntegerField(read_only=True) member_count = serializers.IntegerField(read_only=True)
class Meta: class Meta:
model = VirtualChassis model = VirtualChassis
fields = [ fields = [
'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields', 'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields',
'member_count', 'created', 'last_updated', 'created', 'last_updated', 'member_count',
] ]

View File

@ -579,9 +579,7 @@ class CableTerminationViewSet(NetBoxModelViewSet):
# #
class VirtualChassisViewSet(NetBoxModelViewSet): class VirtualChassisViewSet(NetBoxModelViewSet):
queryset = VirtualChassis.objects.prefetch_related('tags').annotate( queryset = VirtualChassis.objects.prefetch_related('tags')
member_count=count_related(Device, 'virtual_chassis')
)
serializer_class = serializers.VirtualChassisSerializer serializer_class = serializers.VirtualChassisSerializer
filterset_class = filtersets.VirtualChassisFilterSet filterset_class = filtersets.VirtualChassisFilterSet
brief_prefetch_fields = ['master'] brief_prefetch_fields = ['master']

View File

@ -9,7 +9,7 @@ class DCIMConfig(AppConfig):
def ready(self): def ready(self):
from . import signals, search from . import signals, search
from .models import CableTermination, Device from .models import CableTermination, Device, VirtualChassis
from utilities.counters import connect_counters from utilities.counters import connect_counters
# Register denormalized fields # Register denormalized fields
@ -27,4 +27,4 @@ class DCIMConfig(AppConfig):
}) })
# Register counters # Register counters
connect_counters(Device) connect_counters(Device, VirtualChassis)

View File

@ -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
),
]

View File

@ -22,6 +22,7 @@ from netbox.config import ConfigItem
from netbox.models import OrganizationalModel, PrimaryModel from netbox.models import OrganizationalModel, PrimaryModel
from utilities.choices import ColorChoices from utilities.choices import ColorChoices
from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField
from utilities.tracking import TrackingModelMixin
from .device_components import * from .device_components import *
from .mixins import WeightMixin from .mixins import WeightMixin
@ -469,7 +470,7 @@ def update_interface_bridges(device, interface_templates, module=None):
interface.save() 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, 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. 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 blank=True
) )
# Counter fields
member_count = CounterCacheField(
to_model='dcim.Device',
to_field='virtual_chassis'
)
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
verbose_name_plural = 'virtual chassis' verbose_name_plural = 'virtual chassis'

View File

@ -3227,9 +3227,7 @@ class InterfaceConnectionsListView(generic.ObjectListView):
# #
class VirtualChassisListView(generic.ObjectListView): class VirtualChassisListView(generic.ObjectListView):
queryset = VirtualChassis.objects.annotate( queryset = VirtualChassis.objects.all()
member_count=count_related(Device, 'virtual_chassis')
)
table = tables.VirtualChassisTable table = tables.VirtualChassisTable
filterset = filtersets.VirtualChassisFilterSet filterset = filtersets.VirtualChassisFilterSet
filterset_form = forms.VirtualChassisFilterForm filterset_form = forms.VirtualChassisFilterForm

View File

@ -31,6 +31,16 @@
<th scope="row">Description</th> <th scope="row">Description</th>
<td>{{ object.description|placeholder }}</td> <td>{{ object.description|placeholder }}</td>
</tr> </tr>
<tr>
<th scope="row">Members</th>
<td>
{% if object.member_count %}
<a href="{% url 'dcim:device_list' %}?virtual_chassis_id={{ object.pk }}">{{ object.member_count }}</a>
{% else %}
{{ object.member_count }}
{% endif %}
</td>
</tr>
</table> </table>
</div> </div>
</div> </div>