|
1 | 1 | from typing import Annotated, TYPE_CHECKING
|
2 | 2 |
|
3 |
| -from django.db.models import Q |
| 3 | +from django.db.models import QuerySet |
4 | 4 | import strawberry
|
5 | 5 | import strawberry_django
|
6 | 6 | from strawberry.scalars import ID
|
7 |
| -from strawberry_django import FilterLookup |
| 7 | +from strawberry_django import ComparisonFilterLookup, FilterLookup |
8 | 8 |
|
9 | 9 | from core.graphql.filter_mixins import ChangeLogFilterMixin
|
10 | 10 | from dcim import models
|
|
19 | 19 | WeightFilterMixin,
|
20 | 20 | )
|
21 | 21 | from tenancy.graphql.filter_mixins import TenancyFilterMixin, ContactFilterMixin
|
| 22 | +from utilities.query import count_related |
22 | 23 | from .filter_mixins import (
|
23 | 24 | CabledObjectModelFilterMixin,
|
24 | 25 | ComponentModelFilterMixin,
|
@@ -326,6 +327,9 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
|
326 | 327 | )
|
327 | 328 | default_platform_id: ID | None = strawberry_django.filter_field()
|
328 | 329 | part_number: FilterLookup[str] | None = strawberry_django.filter_field()
|
| 330 | + instances: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( |
| 331 | + strawberry_django.filter_field() |
| 332 | + ) |
329 | 333 | u_height: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
|
330 | 334 | strawberry_django.filter_field()
|
331 | 335 | )
|
@@ -384,6 +388,30 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
|
384 | 388 | module_bay_template_count: FilterLookup[int] | None = strawberry_django.filter_field()
|
385 | 389 | inventory_item_template_count: FilterLookup[int] | None = strawberry_django.filter_field()
|
386 | 390 |
|
| 391 | + @strawberry_django.filter_field |
| 392 | + def instance_count( |
| 393 | + self, |
| 394 | + info, |
| 395 | + queryset: QuerySet[models.DeviceType], |
| 396 | + value: ComparisonFilterLookup[int], |
| 397 | + prefix: str, |
| 398 | + ) -> tuple[QuerySet[models.DeviceType], Q]: |
| 399 | + """ |
| 400 | + Filter by the number of related Device instances. |
| 401 | +
|
| 402 | + Annotates each DeviceType with instance_count and applies comparison lookups |
| 403 | + (exact, gt, gte, lt, lte, range). |
| 404 | + """ |
| 405 | + # Annotate each DeviceType with the number of Device instances which use the DeviceType |
| 406 | + qs = queryset.annotate(instance_count=count_related(models.Device, "device_type")) |
| 407 | + # NOTE: include the trailing "__" so Strawberry-Django appends lookups correctly |
| 408 | + return strawberry_django.process_filters( |
| 409 | + filters=value, |
| 410 | + queryset=qs, |
| 411 | + info=info, |
| 412 | + prefix=f"{prefix}instance_count__", |
| 413 | + ) |
| 414 | + |
387 | 415 |
|
388 | 416 | @strawberry_django.filter_type(models.FrontPort, lookups=True)
|
389 | 417 | class FrontPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
|
@@ -665,6 +693,9 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
|
665 | 693 | profile_id: ID | None = strawberry_django.filter_field()
|
666 | 694 | model: FilterLookup[str] | None = strawberry_django.filter_field()
|
667 | 695 | part_number: FilterLookup[str] | None = strawberry_django.filter_field()
|
| 696 | + instances: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( |
| 697 | + strawberry_django.filter_field() |
| 698 | + ) |
668 | 699 | airflow: Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
|
669 | 700 | strawberry_django.filter_field()
|
670 | 701 | )
|
@@ -699,6 +730,30 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
|
699 | 730 | Annotated['InventoryItemTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None
|
700 | 731 | ) = strawberry_django.filter_field()
|
701 | 732 |
|
| 733 | + @strawberry_django.filter_field |
| 734 | + def instance_count( |
| 735 | + self, |
| 736 | + info, |
| 737 | + queryset: QuerySet[models.ModuleType], |
| 738 | + value: ComparisonFilterLookup[int], |
| 739 | + prefix: str, |
| 740 | + ) -> tuple[QuerySet[models.ModuleType], Q]: |
| 741 | + """ |
| 742 | + Filter by the number of related Module instances. |
| 743 | +
|
| 744 | + Annotates each ModuleType with instance_count and applies comparison lookups |
| 745 | + (exact, gt, gte, lt, lte, range). |
| 746 | + """ |
| 747 | + # Annotate each ModuleType with the number of Module instances which use the ModuleType |
| 748 | + qs = queryset.annotate(instance_count=count_related(models.Module, "module_type")) |
| 749 | + # NOTE: include the trailing "__" so Strawberry-Django appends lookups correctly |
| 750 | + return strawberry_django.process_filters( |
| 751 | + filters=value, |
| 752 | + queryset=qs, |
| 753 | + info=info, |
| 754 | + prefix=f"{prefix}instance_count__", |
| 755 | + ) |
| 756 | + |
702 | 757 |
|
703 | 758 | @strawberry_django.filter_type(models.Platform, lookups=True)
|
704 | 759 | class PlatformFilter(OrganizationalModelFilterMixin):
|
|
0 commit comments