Closes #13269: Cache component template counts on device types

This commit is contained in:
Jeremy Stretch 2023-07-25 16:17:58 -04:00
parent daa8f71bb6
commit 5b5444f414
9 changed files with 233 additions and 26 deletions

View File

@ -327,12 +327,28 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True) weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True)
device_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
# Counter fields
console_port_template_count = serializers.IntegerField(read_only=True)
console_server_port_template_count = serializers.IntegerField(read_only=True)
power_port_template_count = serializers.IntegerField(read_only=True)
power_outlet_template_count = serializers.IntegerField(read_only=True)
interface_template_count = serializers.IntegerField(read_only=True)
front_port_template_count = serializers.IntegerField(read_only=True)
rear_port_template_count = serializers.IntegerField(read_only=True)
device_bay_template_count = serializers.IntegerField(read_only=True)
module_bay_template_count = serializers.IntegerField(read_only=True)
inventory_item_template_count = serializers.IntegerField(read_only=True)
class Meta: class Meta:
model = DeviceType model = DeviceType
fields = [ fields = [
'id', 'url', 'display', 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'id', 'url', 'display', 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height',
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image',
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
'console_port_template_count', 'console_server_port_template_count', 'power_port_template_count',
'power_outlet_template_count', 'interface_template_count', 'front_port_template_count',
'rear_port_template_count', 'device_bay_template_count', 'module_bay_template_count',
'inventory_item_template_count',
] ]

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, VirtualChassis from .models import CableTermination, Device, DeviceType, 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, VirtualChassis) connect_counters(Device, DeviceType, VirtualChassis)

View File

@ -32,8 +32,16 @@ def recalculate_device_counts(apps, schema_editor):
device.inventory_item_count = device._inventory_item_count device.inventory_item_count = device._inventory_item_count
Device.objects.bulk_update(devices, [ Device.objects.bulk_update(devices, [
'console_port_count', 'console_server_port_count', 'power_port_count', 'power_outlet_count', 'interface_count', 'console_port_count',
'front_port_count', 'rear_port_count', 'device_bay_count', 'module_bay_count', 'inventory_item_count', 'console_server_port_count',
'power_port_count',
'power_outlet_count',
'interface_count',
'front_port_count',
'rear_port_count',
'device_bay_count',
'module_bay_count',
'inventory_item_count',
]) ])

View File

@ -0,0 +1,108 @@
from django.db import migrations
from django.db.models import Count
import utilities.fields
def recalculate_devicetype_template_counts(apps, schema_editor):
DeviceType = apps.get_model("dcim", "DeviceType")
device_types = list(DeviceType.objects.all().annotate(
_console_port_template_count=Count('consoleporttemplates', distinct=True),
_console_server_port_template_count=Count('consoleserverporttemplates', distinct=True),
_power_port_template_count=Count('powerporttemplates', distinct=True),
_power_outlet_template_count=Count('poweroutlettemplates', distinct=True),
_interface_template_count=Count('interfacetemplates', distinct=True),
_front_port_template_count=Count('frontporttemplates', distinct=True),
_rear_port_template_count=Count('rearporttemplates', distinct=True),
_device_bay_template_count=Count('devicebaytemplates', distinct=True),
_module_bay_template_count=Count('modulebaytemplates', distinct=True),
_inventory_item_template_count=Count('inventoryitemtemplates', distinct=True),
))
for devicetype in device_types:
devicetype.console_port_template_count = devicetype._console_port_template_count
devicetype.console_server_port_template_count = devicetype._console_server_port_template_count
devicetype.power_port_template_count = devicetype._power_port_template_count
devicetype.power_outlet_template_count = devicetype._power_outlet_template_count
devicetype.interface_template_count = devicetype._interface_template_count
devicetype.front_port_template_count = devicetype._front_port_template_count
devicetype.rear_port_template_count = devicetype._rear_port_template_count
devicetype.device_bay_template_count = devicetype._device_bay_template_count
devicetype.module_bay_template_count = devicetype._module_bay_template_count
devicetype.inventory_item_template_count = devicetype._inventory_item_template_count
DeviceType.objects.bulk_update(device_types, [
'console_port_template_count',
'console_server_port_template_count',
'power_port_template_count',
'power_outlet_template_count',
'interface_template_count',
'front_port_template_count',
'rear_port_template_count',
'device_bay_template_count',
'module_bay_template_count',
'inventory_item_template_count',
])
class Migration(migrations.Migration):
dependencies = [
('dcim', '0176_device_component_counters'),
]
operations = [
migrations.AddField(
model_name='devicetype',
name='console_port_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.ConsolePortTemplate'),
),
migrations.AddField(
model_name='devicetype',
name='console_server_port_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.ConsoleServerPortTemplate'),
),
migrations.AddField(
model_name='devicetype',
name='power_port_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.PowerPortTemplate'),
),
migrations.AddField(
model_name='devicetype',
name='power_outlet_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.PowerOutletTemplate'),
),
migrations.AddField(
model_name='devicetype',
name='interface_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.InterfaceTemplate'),
),
migrations.AddField(
model_name='devicetype',
name='front_port_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.FrontPortTemplate'),
),
migrations.AddField(
model_name='devicetype',
name='rear_port_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.RearPortTemplate'),
),
migrations.AddField(
model_name='devicetype',
name='device_bay_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.DeviceBayTemplate'),
),
migrations.AddField(
model_name='devicetype',
name='module_bay_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.ModuleBayTemplate'),
),
migrations.AddField(
model_name='devicetype',
name='inventory_item_template_count',
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.InventoryItemTemplate'),
),
migrations.RunPython(
recalculate_devicetype_template_counts,
reverse_code=migrations.RunPython.noop
),
]

View File

@ -17,7 +17,7 @@ def populate_virtualchassis_members(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('dcim', '0176_device_component_counters'), ('dcim', '0177_devicetype_component_counters'),
] ]
operations = [ operations = [

View File

@ -12,6 +12,7 @@ from netbox.models import ChangeLoggedModel
from utilities.fields import ColorField, NaturalOrderingField from utilities.fields import ColorField, NaturalOrderingField
from utilities.mptt import TreeManager from utilities.mptt import TreeManager
from utilities.ordering import naturalize_interface from utilities.ordering import naturalize_interface
from utilities.tracking import TrackingModelMixin
from .device_components import ( from .device_components import (
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort, ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
RearPort, RearPort,
@ -32,7 +33,7 @@ __all__ = (
) )
class ComponentTemplateModel(ChangeLoggedModel): class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
device_type = models.ForeignKey( device_type = models.ForeignKey(
to='dcim.DeviceType', to='dcim.DeviceType',
on_delete=models.CASCADE, on_delete=models.CASCADE,

View File

@ -129,6 +129,48 @@ class DeviceType(PrimaryModel, WeightMixin):
blank=True blank=True
) )
# Counter fields
console_port_template_count = CounterCacheField(
to_model='dcim.ConsolePortTemplate',
to_field='device_type'
)
console_server_port_template_count = CounterCacheField(
to_model='dcim.ConsoleServerPortTemplate',
to_field='device_type'
)
power_port_template_count = CounterCacheField(
to_model='dcim.PowerPortTemplate',
to_field='device_type'
)
power_outlet_template_count = CounterCacheField(
to_model='dcim.PowerOutletTemplate',
to_field='device_type'
)
interface_template_count = CounterCacheField(
to_model='dcim.InterfaceTemplate',
to_field='device_type'
)
front_port_template_count = CounterCacheField(
to_model='dcim.FrontPortTemplate',
to_field='device_type'
)
rear_port_template_count = CounterCacheField(
to_model='dcim.RearPortTemplate',
to_field='device_type'
)
device_bay_template_count = CounterCacheField(
to_model='dcim.DeviceBayTemplate',
to_field='device_type'
)
module_bay_template_count = CounterCacheField(
to_model='dcim.ModuleBayTemplate',
to_field='device_type'
)
inventory_item_template_count = CounterCacheField(
to_model='dcim.InventoryItemTemplate',
to_field='device_type'
)
images = GenericRelation( images = GenericRelation(
to='extras.ImageAttachment' to='extras.ImageAttachment'
) )

View File

@ -1,4 +1,5 @@
import django_tables2 as tables import django_tables2 as tables
from django.utils.translation import gettext as _
from dcim import models from dcim import models
from netbox.tables import NetBoxTable, columns from netbox.tables import NetBoxTable, columns
@ -83,11 +84,6 @@ class DeviceTypeTable(NetBoxTable):
is_full_depth = columns.BooleanColumn( is_full_depth = columns.BooleanColumn(
verbose_name='Full Depth' verbose_name='Full Depth'
) )
instance_count = columns.LinkedCountColumn(
viewname='dcim:device_list',
url_params={'device_type_id': 'pk'},
verbose_name='Instances'
)
comments = columns.MarkdownColumn() comments = columns.MarkdownColumn()
tags = columns.TagColumn( tags = columns.TagColumn(
url_name='dcim:devicetype_list' url_name='dcim:devicetype_list'
@ -99,12 +95,48 @@ class DeviceTypeTable(NetBoxTable):
template_code=WEIGHT, template_code=WEIGHT,
order_by=('_abs_weight', 'weight_unit') order_by=('_abs_weight', 'weight_unit')
) )
instance_count = columns.LinkedCountColumn(
viewname='dcim:device_list',
url_params={'device_type_id': 'pk'},
verbose_name='Instances'
)
console_port_template_count = tables.Column(
verbose_name=_('Console ports')
)
console_server_port_template_count = tables.Column(
verbose_name=_('Console server ports')
)
power_port_template_count = tables.Column(
verbose_name=_('Power ports')
)
power_outlet_template_count = tables.Column(
verbose_name=_('Power outlets')
)
interface_template_count = tables.Column(
verbose_name=_('Interfaces')
)
front_port_template_count = tables.Column(
verbose_name=_('Front ports')
)
rear_port_template_count = tables.Column(
verbose_name=_('Rear ports')
)
device_bay_template_count = tables.Column(
verbose_name=_('Device bays')
)
module_bay_template_count = tables.Column(
verbose_name=_('Module bays')
)
inventory_item_template_count = tables.Column(
verbose_name=_('Inventory items')
)
class Meta(NetBoxTable.Meta): class Meta(NetBoxTable.Meta):
model = models.DeviceType model = models.DeviceType
fields = ( fields = (
'pk', 'id', 'model', 'manufacturer', 'default_platform', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'pk', 'id', 'model', 'manufacturer', 'default_platform', 'slug', 'part_number', 'u_height', 'is_full_depth',
'airflow', 'weight', 'description', 'comments', 'instance_count', 'tags', 'created', 'last_updated', 'subdevice_role', 'airflow', 'weight', 'description', 'comments', 'instance_count', 'tags', 'created',
'last_updated',
) )
default_columns = ( default_columns = (
'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count', 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count',

View File

@ -951,7 +951,7 @@ class DeviceTypeConsolePortsView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_consoleports' viewname = 'dcim:devicetype_consoleports'
tab = ViewTab( tab = ViewTab(
label=_('Console Ports'), label=_('Console Ports'),
badge=lambda obj: obj.consoleporttemplates.count(), badge=lambda obj: obj.console_port_template_count,
permission='dcim.view_consoleporttemplate', permission='dcim.view_consoleporttemplate',
weight=550, weight=550,
hide_if_empty=True hide_if_empty=True
@ -966,7 +966,7 @@ class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_consoleserverports' viewname = 'dcim:devicetype_consoleserverports'
tab = ViewTab( tab = ViewTab(
label=_('Console Server Ports'), label=_('Console Server Ports'),
badge=lambda obj: obj.consoleserverporttemplates.count(), badge=lambda obj: obj.console_server_port_template_count,
permission='dcim.view_consoleserverporttemplate', permission='dcim.view_consoleserverporttemplate',
weight=560, weight=560,
hide_if_empty=True hide_if_empty=True
@ -981,7 +981,7 @@ class DeviceTypePowerPortsView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_powerports' viewname = 'dcim:devicetype_powerports'
tab = ViewTab( tab = ViewTab(
label=_('Power Ports'), label=_('Power Ports'),
badge=lambda obj: obj.powerporttemplates.count(), badge=lambda obj: obj.power_port_template_count,
permission='dcim.view_powerporttemplate', permission='dcim.view_powerporttemplate',
weight=570, weight=570,
hide_if_empty=True hide_if_empty=True
@ -996,7 +996,7 @@ class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_poweroutlets' viewname = 'dcim:devicetype_poweroutlets'
tab = ViewTab( tab = ViewTab(
label=_('Power Outlets'), label=_('Power Outlets'),
badge=lambda obj: obj.poweroutlettemplates.count(), badge=lambda obj: obj.power_outlet_template_count,
permission='dcim.view_poweroutlettemplate', permission='dcim.view_poweroutlettemplate',
weight=580, weight=580,
hide_if_empty=True hide_if_empty=True
@ -1011,7 +1011,7 @@ class DeviceTypeInterfacesView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_interfaces' viewname = 'dcim:devicetype_interfaces'
tab = ViewTab( tab = ViewTab(
label=_('Interfaces'), label=_('Interfaces'),
badge=lambda obj: obj.interfacetemplates.count(), badge=lambda obj: obj.interface_template_count,
permission='dcim.view_interfacetemplate', permission='dcim.view_interfacetemplate',
weight=520, weight=520,
hide_if_empty=True hide_if_empty=True
@ -1026,7 +1026,7 @@ class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_frontports' viewname = 'dcim:devicetype_frontports'
tab = ViewTab( tab = ViewTab(
label=_('Front Ports'), label=_('Front Ports'),
badge=lambda obj: obj.frontporttemplates.count(), badge=lambda obj: obj.front_port_template_count,
permission='dcim.view_frontporttemplate', permission='dcim.view_frontporttemplate',
weight=530, weight=530,
hide_if_empty=True hide_if_empty=True
@ -1041,7 +1041,7 @@ class DeviceTypeRearPortsView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_rearports' viewname = 'dcim:devicetype_rearports'
tab = ViewTab( tab = ViewTab(
label=_('Rear Ports'), label=_('Rear Ports'),
badge=lambda obj: obj.rearporttemplates.count(), badge=lambda obj: obj.rear_port_template_count,
permission='dcim.view_rearporttemplate', permission='dcim.view_rearporttemplate',
weight=540, weight=540,
hide_if_empty=True hide_if_empty=True
@ -1056,7 +1056,7 @@ class DeviceTypeModuleBaysView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_modulebays' viewname = 'dcim:devicetype_modulebays'
tab = ViewTab( tab = ViewTab(
label=_('Module Bays'), label=_('Module Bays'),
badge=lambda obj: obj.modulebaytemplates.count(), badge=lambda obj: obj.module_bay_template_count,
permission='dcim.view_modulebaytemplate', permission='dcim.view_modulebaytemplate',
weight=510, weight=510,
hide_if_empty=True hide_if_empty=True
@ -1071,7 +1071,7 @@ class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_devicebays' viewname = 'dcim:devicetype_devicebays'
tab = ViewTab( tab = ViewTab(
label=_('Device Bays'), label=_('Device Bays'),
badge=lambda obj: obj.devicebaytemplates.count(), badge=lambda obj: obj.device_bay_template_count,
permission='dcim.view_devicebaytemplate', permission='dcim.view_devicebaytemplate',
weight=500, weight=500,
hide_if_empty=True hide_if_empty=True
@ -1086,7 +1086,7 @@ class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
viewname = 'dcim:devicetype_inventoryitems' viewname = 'dcim:devicetype_inventoryitems'
tab = ViewTab( tab = ViewTab(
label=_('Inventory Items'), label=_('Inventory Items'),
badge=lambda obj: obj.inventoryitemtemplates.count(), badge=lambda obj: obj.inventory_item_template_count,
permission='dcim.view_invenotryitemtemplate', permission='dcim.view_invenotryitemtemplate',
weight=590, weight=590,
hide_if_empty=True hide_if_empty=True