Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions netbox/dcim/api/serializers_/devicetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from dcim.choices import *
from dcim.models import DeviceType, ModuleType, ModuleTypeProfile
from netbox.api.fields import AttributesField, ChoiceField, RelatedObjectCountField
from netbox.api.fields import AttributesField, ChoiceField
from netbox.api.serializers import PrimaryModelSerializer
from netbox.choices import *
from .manufacturers import ManufacturerSerializer
Expand Down Expand Up @@ -45,9 +45,7 @@ class DeviceTypeSerializer(PrimaryModelSerializer):
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)

# Related object counts
device_count = RelatedObjectCountField('instances')
device_count = serializers.IntegerField(read_only=True)

class Meta:
model = DeviceType
Expand Down Expand Up @@ -100,12 +98,13 @@ class ModuleTypeSerializer(PrimaryModelSerializer):
required=False,
allow_null=True
)
module_count = serializers.IntegerField(read_only=True)

class Meta:
model = ModuleType
fields = [
'id', 'url', 'display_url', 'display', 'profile', 'manufacturer', 'model', 'part_number', 'airflow',
'weight', 'weight_unit', 'description', 'attributes', 'owner', 'comments', 'tags', 'custom_fields',
'created', 'last_updated',
'created', 'last_updated', 'module_count',
]
brief_fields = ('id', 'url', 'display', 'profile', 'manufacturer', 'model', 'description')
brief_fields = ('id', 'url', 'display', 'profile', 'manufacturer', 'model', 'description', 'module_count')
9 changes: 4 additions & 5 deletions netbox/dcim/api/serializers_/racks.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,18 @@ class RackBaseSerializer(PrimaryModelSerializer):


class RackTypeSerializer(RackBaseSerializer):
manufacturer = ManufacturerSerializer(
nested=True
)
manufacturer = ManufacturerSerializer(nested=True)
rack_count = serializers.IntegerField(read_only=True)

class Meta:
model = RackType
fields = [
'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'slug', 'description', 'form_factor',
'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
'outer_unit', 'weight', 'max_weight', 'weight_unit', 'mounting_depth', 'description', 'owner', 'comments',
'tags', 'custom_fields', 'created', 'last_updated',
'tags', 'custom_fields', 'created', 'last_updated', 'rack_count',
]
brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description')
brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description', 'rack_count')


class RackSerializer(RackBaseSerializer):
Expand Down
4 changes: 2 additions & 2 deletions netbox/dcim/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def ready(self):
from netbox.models.features import register_models
from utilities.counters import connect_counters
from . import signals, search # noqa: F401
from .models import CableTermination, Device, DeviceType, VirtualChassis
from .models import CableTermination, Device, DeviceType, ModuleType, RackType, VirtualChassis

# Register models
register_models(*self.get_models())
Expand All @@ -31,4 +31,4 @@ def ready(self):
})

# Register counters
connect_counters(Device, DeviceType, VirtualChassis)
connect_counters(Device, DeviceType, ModuleType, RackType, VirtualChassis)
11 changes: 10 additions & 1 deletion netbox/dcim/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ class Meta:
fields = (
'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height',
'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',

# Counters
'rack_count',
)

def search(self, queryset, name, value):
Expand Down Expand Up @@ -627,6 +630,7 @@ class Meta:
'device_bay_template_count',
'module_bay_template_count',
'inventory_item_template_count',
'device_count',
)

def search(self, queryset, name, value):
Expand Down Expand Up @@ -747,7 +751,12 @@ class ModuleTypeFilterSet(AttributeFiltersMixin, PrimaryModelFilterSet):

class Meta:
model = ModuleType
fields = ('id', 'model', 'part_number', 'airflow', 'weight', 'weight_unit', 'description')
fields = (
'id', 'model', 'part_number', 'airflow', 'weight', 'weight_unit', 'description',

# Counters
'module_count',
)

def search(self, queryset, name, value):
if not value.strip():
Expand Down
25 changes: 22 additions & 3 deletions netbox/dcim/forms/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class RackTypeFilterForm(RackBaseFilterForm):
model = RackType
fieldsets = (
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('manufacturer_id', 'form_factor', 'width', 'u_height', name=_('Rack Type')),
FieldSet('manufacturer_id', 'form_factor', 'width', 'u_height', 'rack_count', name=_('Rack Type')),
FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
)
Expand All @@ -327,6 +327,11 @@ class RackTypeFilterForm(RackBaseFilterForm):
required=False,
label=_('Manufacturer')
)
rack_count = forms.IntegerField(
label=_('Rack count'),
required=False,
min_value=0,
)
tag = TagFilterField(model)


Expand Down Expand Up @@ -498,7 +503,8 @@ class DeviceTypeFilterForm(PrimaryModelFilterSetForm):
fieldsets = (
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet(
'manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow', name=_('Hardware')
'manufacturer_id', 'default_platform_id', 'part_number', 'device_count',
'subdevice_role', 'airflow', name=_('Hardware')
),
FieldSet('has_front_image', 'has_rear_image', name=_('Images')),
FieldSet(
Expand All @@ -522,6 +528,11 @@ class DeviceTypeFilterForm(PrimaryModelFilterSetForm):
label=_('Part number'),
required=False
)
device_count = forms.IntegerField(
label=_('Device count'),
required=False,
min_value=0,
)
subdevice_role = forms.MultipleChoiceField(
label=_('Subdevice role'),
choices=add_blank_choice(SubdeviceRoleChoices),
Expand Down Expand Up @@ -633,7 +644,10 @@ class ModuleTypeFilterForm(PrimaryModelFilterSetForm):
model = ModuleType
fieldsets = (
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('profile_id', 'manufacturer_id', 'part_number', 'airflow', name=_('Hardware')),
FieldSet(
'profile_id', 'manufacturer_id', 'part_number', 'module_count',
'airflow', name=_('Hardware')
),
FieldSet(
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
'pass_through_ports', name=_('Components')
Expand All @@ -655,6 +669,11 @@ class ModuleTypeFilterForm(PrimaryModelFilterSetForm):
label=_('Part number'),
required=False
)
module_count = forms.IntegerField(
label=_('Module count'),
required=False,
min_value=0,
)
console_ports = forms.NullBooleanField(
required=False,
label=_('Has console ports'),
Expand Down
12 changes: 11 additions & 1 deletion netbox/dcim/graphql/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import strawberry
import strawberry_django
from strawberry.scalars import ID
from strawberry_django import FilterLookup
from strawberry_django import ComparisonFilterLookup, FilterLookup

from core.graphql.filter_mixins import ChangeLogFilterMixin
from dcim import models
Expand Down Expand Up @@ -328,6 +328,9 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
)
default_platform_id: ID | None = strawberry_django.filter_field()
part_number: FilterLookup[str] | None = strawberry_django.filter_field()
instances: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
u_height: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
Expand Down Expand Up @@ -385,6 +388,7 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
device_bay_template_count: FilterLookup[int] | None = strawberry_django.filter_field()
module_bay_template_count: FilterLookup[int] | None = strawberry_django.filter_field()
inventory_item_template_count: FilterLookup[int] | None = strawberry_django.filter_field()
device_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()


@strawberry_django.filter_type(models.FrontPort, lookups=True)
Expand Down Expand Up @@ -685,6 +689,9 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
profile_id: ID | None = strawberry_django.filter_field()
model: FilterLookup[str] | None = strawberry_django.filter_field()
part_number: FilterLookup[str] | None = strawberry_django.filter_field()
instances: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
airflow: Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
strawberry_django.filter_field()
)
Expand Down Expand Up @@ -718,6 +725,7 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
inventory_item_templates: (
Annotated['InventoryItemTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None
) = strawberry_django.filter_field()
module_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()


@strawberry_django.filter_type(models.Platform, lookups=True)
Expand Down Expand Up @@ -846,6 +854,8 @@ class RackTypeFilter(RackBaseFilterMixin):
manufacturer_id: ID | None = strawberry_django.filter_field()
model: FilterLookup[str] | None = strawberry_django.filter_field()
slug: FilterLookup[str] | None = strawberry_django.filter_field()
racks: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
rack_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()


@strawberry_django.filter_type(models.Rack, lookups=True)
Expand Down
3 changes: 3 additions & 0 deletions netbox/dcim/graphql/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ class DeviceTypeType(PrimaryObjectType):
device_bay_template_count: BigInt
module_bay_template_count: BigInt
inventory_item_template_count: BigInt
device_count: BigInt
front_image: strawberry_django.fields.types.DjangoImageType | None
rear_image: strawberry_django.fields.types.DjangoImageType | None
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
Expand Down Expand Up @@ -605,6 +606,7 @@ class ModuleTypeProfileType(PrimaryObjectType):
pagination=True
)
class ModuleTypeType(PrimaryObjectType):
module_count: BigInt
profile: Annotated["ModuleTypeProfileType", strawberry.lazy('dcim.graphql.types')] | None
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]

Expand Down Expand Up @@ -709,6 +711,7 @@ class PowerPortTemplateType(ModularComponentTemplateType):
pagination=True
)
class RackTypeType(PrimaryObjectType):
rack_count: BigInt
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]


Expand Down
66 changes: 66 additions & 0 deletions netbox/dcim/migrations/0218_devicetype_device_count.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import utilities.fields
from django.db import migrations
from django.db.models import Count, OuterRef, Subquery


def _populate_count_for_type(
apps, schema_editor, app_name: str, model_name: str, target_field: str, related_name: str = 'instances'
):
"""
Update a CounterCache field on the specified model by annotating the count of related instances.
"""
Model = apps.get_model(app_name, model_name)
db_alias = schema_editor.connection.alias

count_subquery = (
Model.objects.using(db_alias)
.filter(pk=OuterRef('pk'))
.annotate(_instance_count=Count(related_name))
.values('_instance_count')
)
Model.objects.using(db_alias).update(**{target_field: Subquery(count_subquery)})


def populate_device_type_device_count(apps, schema_editor):
_populate_count_for_type(apps, schema_editor, 'dcim', 'DeviceType', 'device_count')


def populate_module_type_module_count(apps, schema_editor):
_populate_count_for_type(apps, schema_editor, 'dcim', 'ModuleType', 'module_count')


def populate_rack_type_rack_count(apps, schema_editor):
_populate_count_for_type(apps, schema_editor, 'dcim', 'RackType', 'rack_count', related_name='racks')


class Migration(migrations.Migration):
dependencies = [
('dcim', '0217_owner'),
]

operations = [
migrations.AddField(
model_name='devicetype',
name='device_count',
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.Device'
),
),
migrations.RunPython(populate_device_type_device_count, migrations.RunPython.noop),
migrations.AddField(
model_name='moduletype',
name='module_count',
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='module_type', to_model='dcim.Module'
),
),
migrations.RunPython(populate_module_type_module_count, migrations.RunPython.noop),
migrations.AddField(
model_name='racktype',
name='rack_count',
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='rack_type', to_model='dcim.Rack'
),
),
migrations.RunPython(populate_rack_type_rack_count, migrations.RunPython.noop),
]
4 changes: 4 additions & 0 deletions netbox/dcim/models/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
to_model='dcim.InventoryItemTemplate',
to_field='device_type'
)
device_count = CounterCacheField(
to_model='dcim.Device',
to_field='device_type'
)

clone_fields = (
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight',
Expand Down
8 changes: 7 additions & 1 deletion netbox/dcim/models/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
from netbox.models import PrimaryModel
from netbox.models.features import ImageAttachmentsMixin
from netbox.models.mixins import WeightMixin
from utilities.fields import CounterCacheField
from utilities.jsonschema import validate_schema
from utilities.string import title
from utilities.tracking import TrackingModelMixin
from .device_components import *

__all__ = (
Expand Down Expand Up @@ -92,6 +94,10 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
null=True,
verbose_name=_('attributes')
)
module_count = CounterCacheField(
to_model='dcim.Module',
to_field='module_type'
)

clone_fields = ('profile', 'manufacturer', 'weight', 'weight_unit', 'airflow')
prerequisite_models = (
Expand Down Expand Up @@ -186,7 +192,7 @@ def to_yaml(self):
return yaml.dump(dict(data), sort_keys=False)


class Module(PrimaryModel, ConfigContextModel):
class Module(TrackingModelMixin, PrimaryModel, ConfigContextModel):
"""
A Module represents a field-installable component within a Device which may itself hold multiple device components
(for example, a line card within a chassis switch). Modules are instantiated from ModuleTypes.
Expand Down
Loading