Skip to content

Commit 2fa6c68

Browse files
committed
WIP - filters for type specific data in GraphQL
1 parent 148dd42 commit 2fa6c68

File tree

3 files changed

+711
-8
lines changed

3 files changed

+711
-8
lines changed

cesnet_service_path_plugin/graphql/filters.py

Lines changed: 232 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Annotated, TYPE_CHECKING
1+
# cesnet_service_path_plugin/graphql/filters.py
2+
from typing import Annotated, TYPE_CHECKING, Optional
23

34
import strawberry
45
import strawberry_django
@@ -27,6 +28,32 @@
2728
)
2829

2930

31+
@strawberry.input
32+
class TypeSpecificNumericFilter:
33+
"""Input type for filtering type-specific numeric fields"""
34+
35+
exact: Optional[float] = None
36+
gt: Optional[float] = None
37+
gte: Optional[float] = None
38+
lt: Optional[float] = None
39+
lte: Optional[float] = None
40+
range_min: Optional[float] = None
41+
range_max: Optional[float] = None
42+
43+
44+
@strawberry.input
45+
class TypeSpecificIntegerFilter:
46+
"""Input type for filtering type-specific integer fields"""
47+
48+
exact: Optional[int] = None
49+
gt: Optional[int] = None
50+
gte: Optional[int] = None
51+
lt: Optional[int] = None
52+
lte: Optional[int] = None
53+
range_min: Optional[int] = None
54+
range_max: Optional[int] = None
55+
56+
3057
@strawberry_django.filter(Segment, lookups=True)
3158
class SegmentFilter(NetBoxModelFilterMixin):
3259
"""GraphQL filter for Segment model"""
@@ -71,6 +98,210 @@ class SegmentFilter(NetBoxModelFilterMixin):
7198
strawberry_django.filter_field()
7299
)
73100

101+
# Custom filter for checking if segment has path data
102+
has_path_data: Optional[bool] = None
103+
104+
# Custom filter for checking if segment has type-specific data
105+
has_type_specific_data: Optional[bool] = None
106+
107+
# Type-specific field filters - Dark Fiber
108+
fiber_type: FilterLookup[str] | None = None
109+
fiber_attenuation_max: Optional[TypeSpecificNumericFilter] = None
110+
total_loss: Optional[TypeSpecificNumericFilter] = None
111+
total_length: Optional[TypeSpecificNumericFilter] = None
112+
number_of_fibers: Optional[TypeSpecificIntegerFilter] = None
113+
connector_type: FilterLookup[str] | None = None
114+
115+
# Type-specific field filters - Optical Spectrum
116+
wavelength: Optional[TypeSpecificNumericFilter] = None
117+
spectral_slot_width: Optional[TypeSpecificNumericFilter] = None
118+
itu_grid_position: Optional[TypeSpecificIntegerFilter] = None
119+
modulation_format: FilterLookup[str] | None = None
120+
121+
# Type-specific field filters - Ethernet Service
122+
port_speed: Optional[TypeSpecificIntegerFilter] = None
123+
vlan_id: Optional[TypeSpecificIntegerFilter] = None
124+
mtu_size: Optional[TypeSpecificIntegerFilter] = None
125+
encapsulation_type: FilterLookup[str] | None = None
126+
interface_type: FilterLookup[str] | None = None
127+
128+
def filter_has_path_data(self, queryset, info):
129+
if self.has_path_data is None:
130+
return queryset
131+
if self.has_path_data:
132+
return queryset.filter(path_geometry__isnull=False)
133+
else:
134+
return queryset.filter(path_geometry__isnull=True)
135+
136+
def filter_has_type_specific_data(self, queryset, info):
137+
if self.has_type_specific_data is None:
138+
return queryset
139+
if self.has_type_specific_data:
140+
return queryset.exclude(type_specific_data={})
141+
else:
142+
return queryset.filter(type_specific_data={})
143+
144+
def filter_fiber_type(self, queryset, info):
145+
if self.fiber_type is None:
146+
return queryset
147+
return queryset.filter(type_specific_data__fiber_type=self.fiber_type)
148+
149+
def filter_connector_type(self, queryset, info):
150+
if self.connector_type is None:
151+
return queryset
152+
return queryset.filter(type_specific_data__connector_type=self.connector_type)
153+
154+
def filter_modulation_format(self, queryset, info):
155+
if self.modulation_format is None:
156+
return queryset
157+
return queryset.filter(type_specific_data__modulation_format=self.modulation_format)
158+
159+
def filter_encapsulation_type(self, queryset, info):
160+
if self.encapsulation_type is None:
161+
return queryset
162+
return queryset.filter(type_specific_data__encapsulation_type=self.encapsulation_type)
163+
164+
def filter_interface_type(self, queryset, info):
165+
if self.interface_type is None:
166+
return queryset
167+
return queryset.filter(type_specific_data__interface_type=self.interface_type)
168+
169+
def _filter_numeric_field(self, queryset, field_name: str, filter_input: TypeSpecificNumericFilter):
170+
"""Helper method to filter numeric type-specific fields"""
171+
if filter_input is None:
172+
return queryset
173+
174+
conditions = Q(type_specific_data__has_key=field_name)
175+
176+
if filter_input.exact is not None:
177+
conditions &= Q(
178+
pk__in=queryset.extra(
179+
where=["(type_specific_data->>%s)::decimal = %s"], params=[field_name, filter_input.exact]
180+
).values("pk")
181+
)
182+
183+
if filter_input.gt is not None:
184+
conditions &= Q(
185+
pk__in=queryset.extra(
186+
where=["(type_specific_data->>%s)::decimal > %s"], params=[field_name, filter_input.gt]
187+
).values("pk")
188+
)
189+
190+
if filter_input.gte is not None:
191+
conditions &= Q(
192+
pk__in=queryset.extra(
193+
where=["(type_specific_data->>%s)::decimal >= %s"], params=[field_name, filter_input.gte]
194+
).values("pk")
195+
)
196+
197+
if filter_input.lt is not None:
198+
conditions &= Q(
199+
pk__in=queryset.extra(
200+
where=["(type_specific_data->>%s)::decimal < %s"], params=[field_name, filter_input.lt]
201+
).values("pk")
202+
)
203+
204+
if filter_input.lte is not None:
205+
conditions &= Q(
206+
pk__in=queryset.extra(
207+
where=["(type_specific_data->>%s)::decimal <= %s"], params=[field_name, filter_input.lte]
208+
).values("pk")
209+
)
210+
211+
if filter_input.range_min is not None and filter_input.range_max is not None:
212+
conditions &= Q(
213+
pk__in=queryset.extra(
214+
where=["(type_specific_data->>%s)::decimal BETWEEN %s AND %s"],
215+
params=[field_name, filter_input.range_min, filter_input.range_max],
216+
).values("pk")
217+
)
218+
219+
return queryset.filter(conditions)
220+
221+
def _filter_integer_field(self, queryset, field_name: str, filter_input: TypeSpecificIntegerFilter):
222+
"""Helper method to filter integer type-specific fields"""
223+
if filter_input is None:
224+
return queryset
225+
226+
conditions = Q(type_specific_data__has_key=field_name)
227+
228+
if filter_input.exact is not None:
229+
conditions &= Q(
230+
pk__in=queryset.extra(
231+
where=["(type_specific_data->>%s)::integer = %s"], params=[field_name, filter_input.exact]
232+
).values("pk")
233+
)
234+
235+
if filter_input.gt is not None:
236+
conditions &= Q(
237+
pk__in=queryset.extra(
238+
where=["(type_specific_data->>%s)::integer > %s"], params=[field_name, filter_input.gt]
239+
).values("pk")
240+
)
241+
242+
if filter_input.gte is not None:
243+
conditions &= Q(
244+
pk__in=queryset.extra(
245+
where=["(type_specific_data->>%s)::integer >= %s"], params=[field_name, filter_input.gte]
246+
).values("pk")
247+
)
248+
249+
if filter_input.lt is not None:
250+
conditions &= Q(
251+
pk__in=queryset.extra(
252+
where=["(type_specific_data->>%s)::integer < %s"], params=[field_name, filter_input.lt]
253+
).values("pk")
254+
)
255+
256+
if filter_input.lte is not None:
257+
conditions &= Q(
258+
pk__in=queryset.extra(
259+
where=["(type_specific_data->>%s)::integer <= %s"], params=[field_name, filter_input.lte]
260+
).values("pk")
261+
)
262+
263+
if filter_input.range_min is not None and filter_input.range_max is not None:
264+
conditions &= Q(
265+
pk__in=queryset.extra(
266+
where=["(type_specific_data->>%s)::integer BETWEEN %s AND %s"],
267+
params=[field_name, filter_input.range_min, filter_input.range_max],
268+
).values("pk")
269+
)
270+
271+
return queryset.filter(conditions)
272+
273+
# Apply filters for numeric fields
274+
def filter_fiber_attenuation_max(self, queryset, info):
275+
return self._filter_numeric_field(queryset, "fiber_attenuation_max", self.fiber_attenuation_max)
276+
277+
def filter_total_loss(self, queryset, info):
278+
return self._filter_numeric_field(queryset, "total_loss", self.total_loss)
279+
280+
def filter_total_length(self, queryset, info):
281+
return self._filter_numeric_field(queryset, "total_length", self.total_length)
282+
283+
def filter_wavelength(self, queryset, info):
284+
return self._filter_numeric_field(queryset, "wavelength", self.wavelength)
285+
286+
def filter_spectral_slot_width(self, queryset, info):
287+
return self._filter_numeric_field(queryset, "spectral_slot_width", self.spectral_slot_width)
288+
289+
# Apply filters for integer fields
290+
def filter_number_of_fibers(self, queryset, info):
291+
return self._filter_integer_field(queryset, "number_of_fibers", self.number_of_fibers)
292+
293+
def filter_itu_grid_position(self, queryset, info):
294+
return self._filter_integer_field(queryset, "itu_grid_position", self.itu_grid_position)
295+
296+
def filter_port_speed(self, queryset, info):
297+
return self._filter_integer_field(queryset, "port_speed", self.port_speed)
298+
299+
def filter_vlan_id(self, queryset, info):
300+
return self._filter_integer_field(queryset, "vlan_id", self.vlan_id)
301+
302+
def filter_mtu_size(self, queryset, info):
303+
return self._filter_integer_field(queryset, "mtu_size", self.mtu_size)
304+
74305

75306
@strawberry_django.filter(ServicePath, lookups=True)
76307
class ServicePathFilter(NetBoxModelFilterMixin):

cesnet_service_path_plugin/graphql/types.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,6 @@ def segment_type_display(self) -> Optional[str]:
8181
return self.get_segment_type_display()
8282
return None
8383

84-
@field
85-
def type_specific_display(self) -> Optional[strawberry.scalars.JSON]:
86-
"""Formatted display of type-specific data"""
87-
if hasattr(self, "get_type_specific_display"):
88-
return self.get_type_specific_display()
89-
return None
90-
9184
@field
9285
def type_specific_schema(self) -> Optional[strawberry.scalars.JSON]:
9386
"""Schema for the segment's type"""

0 commit comments

Comments
 (0)