From 396a9a6ebeda4926d5a26757843a0e009b89bfb7 Mon Sep 17 00:00:00 2001 From: Josh Niec Date: Fri, 22 May 2026 13:09:22 -0400 Subject: [PATCH 01/58] fix: make id field required in bulk patch/put open api schema --- netbox/core/api/schema.py | 22 ++++++++++-- netbox/netbox/api/serializers/bulk.py | 50 ++++++++++++++++++++++++++- netbox/netbox/api/viewsets/mixins.py | 15 ++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/netbox/core/api/schema.py b/netbox/core/api/schema.py index 5ceb3c61d..1105ca11a 100644 --- a/netbox/core/api/schema.py +++ b/netbox/core/api/schema.py @@ -138,14 +138,30 @@ class NetBoxAutoSchema(AutoSchema): return super().get_operation_id() def get_request_serializer(self) -> typing.Any: - # bulk operations should specify a list serializer = super().get_request_serializer() + # Bulk update/partial-update has a special request shape: a list of + # writable objects plus a required `id` field. The normal writable + # serializer omits `id` because it is read-only, so don't use the generic + # bulk handling for these actions. + action = getattr(self.view, 'action', None) + if action in ('bulk_update', 'bulk_partial_update'): + get_bulk_update_request_serializer = getattr( + self.view, + 'get_bulk_update_request_serializer', + None, + ) + if get_bulk_update_request_serializer is not None: + return get_bulk_update_request_serializer( + partial=(action == 'bulk_partial_update' or self.method == 'PATCH') + ) + + # Bulk creates/deletes should specify a list. if self.is_bulk_action: return type(serializer)(many=True) - # handle mapping for Writable serializers - adapted from dansheps original code - # for drf-yasg + # handle mapping for Writable serializers - adapted from dansheps original + # code for drf-yasg. if serializer is not None and self.method in WRITABLE_ACTIONS: writable_class = self.get_writable_class(serializer) if writable_class is not None: diff --git a/netbox/netbox/api/serializers/bulk.py b/netbox/netbox/api/serializers/bulk.py index c9fd28534..e8c46be1f 100644 --- a/netbox/netbox/api/serializers/bulk.py +++ b/netbox/netbox/api/serializers/bulk.py @@ -1,11 +1,59 @@ +from functools import lru_cache + +from rest_framework import serializers from rest_framework import serializers from .features import ChangeLogMessageSerializer __all__ = ( - 'BulkOperationSerializer', + 'BulkOperationSerializer', + 'BulkPartialUpdateSchemaMixin', + 'get_bulk_update_serializer_class' ) +class BulkPartialUpdateSchemaMixin: + def get_fields(self): + fields = super().get_fields() + fields['id'] = serializers.IntegerField(required=True) + + for name, field in fields.items(): + if name != 'id': + field.required = False + + return fields + + +@lru_cache +def get_bulk_update_serializer_class(serializer_class, *, partial=False): + """ + Return a schema-only serializer for bulk PUT/PATCH requests. + + Bulk update requests to a list endpoint require each object to include + the target object's numeric ID, even though `id` is read-only on the + normal model serializer. The runtime code consumes `id` before invoking + the model serializer for each object. + """ + meta = getattr(serializer_class, 'Meta') + + class Meta(meta): + fields = ('id', *[f for f in meta.fields if f != 'id']) + + bases = ( + (BulkPartialUpdateSchemaMixin, serializer_class) + if partial + else (serializer_class,) + ) + + attrs = { + 'id': serializers.IntegerField(required=True), + 'Meta': Meta, + '__module__': serializer_class.__module__, + } + + prefix = 'PatchedBulk' if partial else 'Bulk' + return type(f'{prefix}{serializer_class.__name__}', bases, attrs) + + class BulkOperationSerializer(ChangeLogMessageSerializer): id = serializers.IntegerField() diff --git a/netbox/netbox/api/viewsets/mixins.py b/netbox/netbox/api/viewsets/mixins.py index fd49bd7af..7caf38480 100644 --- a/netbox/netbox/api/viewsets/mixins.py +++ b/netbox/netbox/api/viewsets/mixins.py @@ -7,6 +7,7 @@ from rest_framework.response import Response from core.models import ObjectType from extras.models import ExportTemplate from netbox.api.serializers import BulkOperationSerializer +from netbox.api.serializers.bulk import get_bulk_update_serializer_class __all__ = ( 'BulkDestroyModelMixin', @@ -133,6 +134,20 @@ class BulkUpdateModelMixin: return updated_pks + def get_bulk_update_serializer_class(self, *, partial=False): + return get_bulk_update_serializer_class( + self.get_serializer_class(), + partial=partial, + ) + + def get_bulk_update_request_serializer(self, *, partial=False): + serializer_class = self.get_bulk_update_serializer_class(partial=partial) + + # Important: do NOT pass partial=True here. The partial schema class already + # makes non-id fields optional, and passing partial=True would also make id + # appear optional in OpenAPI. + return serializer_class(many=True) + def bulk_partial_update(self, request, *args, **kwargs): kwargs['partial'] = True return self.bulk_update(request, *args, **kwargs) From cfdf22fc185820726f23cb218911288a60757bba Mon Sep 17 00:00:00 2001 From: Josh Niec Date: Fri, 22 May 2026 14:08:05 -0400 Subject: [PATCH 02/58] fix: linting --- netbox/netbox/api/serializers/bulk.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/netbox/netbox/api/serializers/bulk.py b/netbox/netbox/api/serializers/bulk.py index e8c46be1f..5817155e7 100644 --- a/netbox/netbox/api/serializers/bulk.py +++ b/netbox/netbox/api/serializers/bulk.py @@ -1,12 +1,11 @@ from functools import lru_cache -from rest_framework import serializers from rest_framework import serializers from .features import ChangeLogMessageSerializer __all__ = ( - 'BulkOperationSerializer', + 'BulkOperationSerializer', 'BulkPartialUpdateSchemaMixin', 'get_bulk_update_serializer_class' ) From b55b50b12e0c6d378c15888b404a037c5b9f150d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 2 Jun 2026 10:43:52 -0400 Subject: [PATCH 03/58] CAP-122: Add GitHub workflow to close new issues missing labels (#22356) --- .github/workflows/no-blank-issue.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/no-blank-issue.yml diff --git a/.github/workflows/no-blank-issue.yml b/.github/workflows/no-blank-issue.yml new file mode 100644 index 000000000..0cb5a8f84 --- /dev/null +++ b/.github/workflows/no-blank-issue.yml @@ -0,0 +1,21 @@ +name: Enforce issue templates + +on: + issues: + types: + - opened + - reopened + +permissions: + issues: write + +jobs: + no-blank-issue: + name: No Blank Issue + runs-on: ubuntu-slim + + steps: + - name: Close new issues without labels + uses: ldez/no-blank-issue@800e2d0c81c9e0ca7bdb58f3e7480a74602d91e0 # v1.2.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} From 35450a6cb8ae4981b4a62ef45985e79798bc2779 Mon Sep 17 00:00:00 2001 From: bctiemann Date: Tue, 2 Jun 2026 11:25:56 -0400 Subject: [PATCH 04/58] Fixes #22251: Re-parent child ModuleBays when a Module is moved to a new bay (#22336) --- netbox/dcim/models/modules.py | 11 ++++ netbox/dcim/tests/test_models.py | 97 ++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/netbox/dcim/models/modules.py b/netbox/dcim/models/modules.py index 857945d6c..f52367c6d 100644 --- a/netbox/dcim/models/modules.py +++ b/netbox/dcim/models/modules.py @@ -326,9 +326,20 @@ class Module(TrackingModelMixin, PrimaryModel, ConfigContextModel): def save(self, *args, **kwargs): is_new = self.pk is None + old_module_bay_id = None + + if not is_new: + old_module_bay_id = Module.objects.filter(pk=self.pk).values_list( + 'module_bay_id', flat=True + ).first() super().save(*args, **kwargs) + if old_module_bay_id is not None and old_module_bay_id != self.module_bay_id: + for child_bay in self.modulebays.select_related('module__module_bay'): + child_bay.snapshot() + child_bay.save() + adopt_components = getattr(self, '_adopt_components', False) disable_replication = getattr(self, '_disable_replication', False) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 260615b4e..b266e9cde 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -1188,6 +1188,103 @@ class ModuleBayTestCase(TestCase): self.assertEqual(movable_bay.parent_id, host_bay.pk) self.assertEqual(movable_bay.tree_id, host_bay.tree_id) + @tag('regression') # #22251 + def test_moving_module_reparents_child_module_bays(self): + """ + When a module is moved to a different module bay, each child ModuleBay + (a bay that belongs to the module) must have its MPTT parent updated + to the new host bay. Without the fix the children stay parented to the + old bay even though Module.module_bay_id has changed. + """ + device_type = DeviceType.objects.first() + device_role = DeviceRole.objects.first() + site = Site.objects.first() + device = Device.objects.create( + name='Move Module Device', + device_type=device_type, + role=device_role, + site=site, + ) + bay_a = ModuleBay.objects.create(device=device, name='Bay A') + bay_b = ModuleBay.objects.create(device=device, name='Bay B') + + manufacturer = Manufacturer.objects.first() + module_type = ModuleType.objects.create( + manufacturer=manufacturer, model='Move Module Type' + ) + module = Module.objects.create( + device=device, module_bay=bay_a, module_type=module_type + ) + + child_1 = ModuleBay.objects.create(device=device, module=module, name='Child Bay 1') + child_2 = ModuleBay.objects.create(device=device, module=module, name='Child Bay 2') + self.assertEqual(child_1.parent_id, bay_a.pk) + self.assertEqual(child_2.parent_id, bay_a.pk) + + # Move the module to bay_b. + module.module_bay = bay_b + module.save() + + child_1.refresh_from_db() + child_2.refresh_from_db() + self.assertEqual(child_1.parent_id, bay_b.pk) + self.assertEqual(child_2.parent_id, bay_b.pk) + # Children must share the same MPTT tree as their new parent. + bay_b.refresh_from_db() + self.assertEqual(child_1.tree_id, bay_b.tree_id) + self.assertEqual(child_2.tree_id, bay_b.tree_id) + + @tag('regression') # #22251 + def test_moving_module_reparents_grandchild_module_bays(self): + """ + When a module is moved, grandchild ModuleBays (bays inside a module + that is itself installed inside a child bay of the moved module) must + also land in the new MPTT tree. MPTT moves subtrees atomically, so + calling save() only on direct children is sufficient — this test + documents and preserves that invariant for future tree-backend changes. + """ + device_type = DeviceType.objects.first() + device_role = DeviceRole.objects.first() + site = Site.objects.first() + device = Device.objects.create( + name='Grandchild Move Device', + device_type=device_type, + role=device_role, + site=site, + ) + bay_a = ModuleBay.objects.create(device=device, name='Bay A') + bay_b = ModuleBay.objects.create(device=device, name='Bay B') + + manufacturer = Manufacturer.objects.first() + module_type = ModuleType.objects.create( + manufacturer=manufacturer, model='Grandchild Move Type' + ) + # Depth-1: module installed in bay_a, with one child bay. + module_1 = Module.objects.create(device=device, module_bay=bay_a, module_type=module_type) + child_bay = ModuleBay.objects.create(device=device, module=module_1, name='Child Bay') + + # Depth-2: module installed in child_bay, with one grandchild bay. + module_2 = Module.objects.create(device=device, module_bay=child_bay, module_type=module_type) + grandchild_bay = ModuleBay.objects.create(device=device, module=module_2, name='Grandchild Bay') + + self.assertEqual(child_bay.parent_id, bay_a.pk) + self.assertEqual(grandchild_bay.parent_id, child_bay.pk) + self.assertEqual(grandchild_bay.tree_id, bay_a.tree_id) + + # Move the top-level module to bay_b. + module_1.module_bay = bay_b + module_1.save() + + child_bay.refresh_from_db() + grandchild_bay.refresh_from_db() + bay_b.refresh_from_db() + + self.assertEqual(child_bay.parent_id, bay_b.pk) + self.assertEqual(child_bay.tree_id, bay_b.tree_id) + # Grandchild's direct parent (child_bay) is unchanged; only tree placement moves. + self.assertEqual(grandchild_bay.parent_id, child_bay.pk) + self.assertEqual(grandchild_bay.tree_id, bay_b.tree_id) + def test_single_module_token(self): device_type = DeviceType.objects.first() device_role = DeviceRole.objects.first() From fc17d468aa92a788382415fb37032b09903078aa Mon Sep 17 00:00:00 2001 From: Maksym-Ototiuk Date: Thu, 28 May 2026 12:32:45 +0000 Subject: [PATCH 05/58] Closes #21666: Add MU fiber connector type --- netbox/dcim/choices.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 514d4aedf..58011b5f7 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1637,6 +1637,10 @@ class PortTypeChoices(ChoiceSet): TYPE_LC_PC = 'lc-pc' TYPE_LC_UPC = 'lc-upc' TYPE_LC_APC = 'lc-apc' + TYPE_MU = 'mu' + TYPE_MU_PC = 'mu-pc' + TYPE_MU_UPC = 'mu-upc' + TYPE_MU_APC = 'mu-apc' TYPE_MTRJ = 'mtrj' TYPE_MPO = 'mpo' TYPE_LSH = 'lsh' @@ -1700,6 +1704,10 @@ class PortTypeChoices(ChoiceSet): (TYPE_LC_PC, 'LC/PC'), (TYPE_LC_UPC, 'LC/UPC'), (TYPE_LC_APC, 'LC/APC'), + (TYPE_MU, 'MU'), + (TYPE_MU_PC, 'MU/PC'), + (TYPE_MU_UPC, 'MU/UPC'), + (TYPE_MU_APC, 'MU/APC'), (TYPE_LSH, 'LSH'), (TYPE_LSH_PC, 'LSH/PC'), (TYPE_LSH_UPC, 'LSH/UPC'), From 839259ccecdac156c8c0b52678008513fed17758 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 2 Jun 2026 12:17:02 -0400 Subject: [PATCH 06/58] Closes #22361: Introduce ArrayAttr UI panel attribute (#22362) --- docs/plugins/development/ui-components.md | 39 ++++++++++++----------- netbox/netbox/tests/test_ui.py | 24 ++++++++++++++ netbox/netbox/ui/attrs.py | 17 ++++++++++ 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/docs/plugins/development/ui-components.md b/docs/plugins/development/ui-components.md index 45766b201..f148443e3 100644 --- a/docs/plugins/development/ui-components.md +++ b/docs/plugins/development/ui-components.md @@ -135,29 +135,32 @@ panels.ObjectsTablePanel( The following classes are available to represent object attributes within an ObjectAttributesPanel. Additionally, plugins can subclass `netbox.ui.attrs.ObjectAttribute` to create custom classes. -| Class | Description | -|------------------------------------------|--------------------------------------------------| -| `netbox.ui.attrs.AddressAttr` | A physical or mailing address. | -| `netbox.ui.attrs.BooleanAttr` | A boolean value | -| `netbox.ui.attrs.ChoiceAttr` | A selection from a set of choices | -| `netbox.ui.attrs.ColorAttr` | A color expressed in RGB | -| `netbox.ui.attrs.DateTimeAttr` | A date or datetime value | -| `netbox.ui.attrs.GenericForeignKeyAttr` | A related object via a generic foreign key | -| `netbox.ui.attrs.GPSCoordinatesAttr` | GPS coordinates (latitude and longitude) | -| `netbox.ui.attrs.ImageAttr` | An attached image (displays the image) | -| `netbox.ui.attrs.NestedObjectAttr` | A related nested object (includes ancestors) | -| `netbox.ui.attrs.NumericAttr` | An integer or float value | -| `netbox.ui.attrs.RelatedObjectAttr` | A related object | -| `netbox.ui.attrs.RelatedObjectListAttr` | A list of related objects | -| `netbox.ui.attrs.TemplatedAttr` | Renders an attribute using a custom template | -| `netbox.ui.attrs.TextAttr` | A string (text) value | -| `netbox.ui.attrs.TimezoneAttr` | A timezone with annotated offset | -| `netbox.ui.attrs.UtilizationAttr` | A numeric value expressed as a utilization graph | +| Class | Description | +|-----------------------------------------|-----------------------------------------------------| +| `netbox.ui.attrs.AddressAttr` | A physical or mailing address. | +| `netbox.ui.attrs.ArrayAttr` | An array of values, shown as a comma-separated list | +| `netbox.ui.attrs.BooleanAttr` | A boolean value | +| `netbox.ui.attrs.ChoiceAttr` | A selection from a set of choices | +| `netbox.ui.attrs.ColorAttr` | A color expressed in RGB | +| `netbox.ui.attrs.DateTimeAttr` | A date or datetime value | +| `netbox.ui.attrs.GenericForeignKeyAttr` | A related object via a generic foreign key | +| `netbox.ui.attrs.GPSCoordinatesAttr` | GPS coordinates (latitude and longitude) | +| `netbox.ui.attrs.ImageAttr` | An attached image (displays the image) | +| `netbox.ui.attrs.NestedObjectAttr` | A related nested object (includes ancestors) | +| `netbox.ui.attrs.NumericAttr` | An integer or float value | +| `netbox.ui.attrs.RelatedObjectAttr` | A related object | +| `netbox.ui.attrs.RelatedObjectListAttr` | A list of related objects | +| `netbox.ui.attrs.TemplatedAttr` | Renders an attribute using a custom template | +| `netbox.ui.attrs.TextAttr` | A string (text) value | +| `netbox.ui.attrs.TimezoneAttr` | A timezone with annotated offset | +| `netbox.ui.attrs.UtilizationAttr` | A numeric value expressed as a utilization graph | ::: netbox.ui.attrs.ObjectAttribute ::: netbox.ui.attrs.AddressAttr +::: netbox.ui.attrs.ArrayAttr + ::: netbox.ui.attrs.BooleanAttr ::: netbox.ui.attrs.ChoiceAttr diff --git a/netbox/netbox/tests/test_ui.py b/netbox/netbox/tests/test_ui.py index 55cbb3842..9ecd91ef0 100644 --- a/netbox/netbox/tests/test_ui.py +++ b/netbox/netbox/tests/test_ui.py @@ -3,6 +3,7 @@ from types import SimpleNamespace from django.template import Context, Template from django.test import RequestFactory, SimpleTestCase, TestCase +from netaddr import IPNetwork from circuits.choices import CircuitStatusChoices, VirtualCircuitTerminationRoleChoices from circuits.models import ( @@ -250,6 +251,29 @@ class TextAttrTestCase(TestCase): self.assertTrue(context['copy_button']) +class ArrayAttrTestCase(TestCase): + + def test_get_value(self): + attr = attrs.ArrayAttr('allowed_ips') + obj = SimpleNamespace(allowed_ips=[IPNetwork('192.168.1.1/32'), IPNetwork('2001:db8::/64')]) + self.assertEqual(attr.get_value(obj), '192.168.1.1/32, 2001:db8::/64') + + def test_get_value_empty(self): + attr = attrs.ArrayAttr('allowed_ips') + obj = SimpleNamespace(allowed_ips=[]) + self.assertIsNone(attr.get_value(obj)) + + def test_get_value_none(self): + attr = attrs.ArrayAttr('allowed_ips') + obj = SimpleNamespace(allowed_ips=None) + self.assertIsNone(attr.get_value(obj)) + + def test_get_value_with_format_string(self): + attr = attrs.ArrayAttr('ports', format_string='{}/tcp') + obj = SimpleNamespace(ports=[80, 443]) + self.assertEqual(attr.get_value(obj), '80/tcp, 443/tcp') + + class NumericAttrTestCase(TestCase): def test_get_context_with_unit_accessor(self): diff --git a/netbox/netbox/ui/attrs.py b/netbox/netbox/ui/attrs.py index 8734fb4a1..60a14d33a 100644 --- a/netbox/netbox/ui/attrs.py +++ b/netbox/netbox/ui/attrs.py @@ -8,6 +8,7 @@ from utilities.data import resolve_attr_path __all__ = ( 'AddressAttr', + 'ArrayAttr', 'BooleanAttr', 'ChoiceAttr', 'ColorAttr', @@ -141,6 +142,22 @@ class TextAttr(ObjectAttribute): } +class ArrayAttr(TextAttr): + """ + An attribute comprising an array of values, rendered as a comma-separated list. If specified, `format_string` + is applied to each item individually. Null and empty arrays are treated as equivalent: both render as the + placeholder. + """ + + def get_value(self, obj): + value = resolve_attr_path(obj, self.accessor) + if not value: + return None + if self.format_string: + return ', '.join(self.format_string.format(v) for v in value) + return ', '.join(str(v) for v in value) + + class NumericAttr(ObjectAttribute): """ An integer or float attribute. From 5b0854124266fe95b30e495af7b643dce71c3ce2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 2 Jun 2026 12:39:05 -0400 Subject: [PATCH 07/58] Fixes #22340: Correct display of allowed IPs for tokens in web UI --- netbox/users/ui/panels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/users/ui/panels.py b/netbox/users/ui/panels.py index 35a40b88b..c7c51e381 100644 --- a/netbox/users/ui/panels.py +++ b/netbox/users/ui/panels.py @@ -14,7 +14,7 @@ class TokenPanel(panels.ObjectAttributesPanel): write_enabled = attrs.BooleanAttr('write_enabled') expires = attrs.TextAttr('expires') last_used = attrs.TextAttr('last_used') - allowed_ips = attrs.TextAttr('allowed_ips') + allowed_ips = attrs.ArrayAttr('allowed_ips', label=_('Allowed IPs')) class TokenExamplePanel(panels.Panel): From 3172e479049ab229b40dbbe9cefd8261fad12435 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Tue, 2 Jun 2026 22:30:17 +0200 Subject: [PATCH 08/58] Fixes #22210: Respect filters when rendering IPAM child availability views (#22327) * fix(ipam): Honor filters for child availability views Retain the instantiated child FilterSet on ObjectChildrenView and expose whether child object filters are active. Use this in IPAM child views to avoid rendering synthetic availability rows when the child queryset has been filtered. This ensures Saved Filters and direct filters are respected on Prefix IP Address, Child Prefix, Aggregate Prefix, and VLAN Group VLAN tabs. Fixes #22210 * refactor(ipam): Replace has_active_filters with ChildAvailabilityMixin Extracts filter detection logic from ObjectChildrenView into a dedicated ChildAvailabilityMixin. Compares WHERE clause signatures between filtered and unfiltered querysets instead of inspecting filter parameters, improving reliability when child querysets are pre-scoped to parent objects. --- netbox/ipam/tests/test_views.py | 302 ++++++++++++++++++++++++++++++++ netbox/ipam/views.py | 95 ++++++++-- 2 files changed, 387 insertions(+), 10 deletions(-) diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 10932ce72..5505d2a5c 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -1,6 +1,7 @@ import datetime from django.contrib.contenttypes.models import ContentType +from django.test import RequestFactory from django.urls import reverse from netaddr import IPNetwork @@ -8,8 +9,10 @@ from core.choices import ObjectChangeActionChoices from core.models import ObjectChange, ObjectType from dcim.constants import InterfaceTypeChoices from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site +from extras.models import SavedFilter from ipam.choices import * from ipam.models import * +from ipam.views import AggregatePrefixesView from netbox.choices import CSVDelimiterChoices, ImportFormatChoices from tenancy.models import Tenant from users.models import ObjectPermission @@ -353,6 +356,101 @@ class AggregateTestCase(ViewTestCases.PrimaryObjectViewTestCase): url = reverse('ipam:aggregate_prefixes', kwargs={'pk': aggregate.pk}) self.assertHttpStatus(self.client.get(url), 200) + def test_aggregate_prefixes_filter_suppresses_available_prefixes(self): + self.add_permissions('ipam.view_aggregate', 'ipam.view_prefix') + + tenants = ( + Tenant(name='Aggregate Tenant 1', slug='aggregate-tenant-1'), + Tenant(name='Aggregate Tenant 2', slug='aggregate-tenant-2'), + ) + Tenant.objects.bulk_create(tenants) + + aggregate = Aggregate.objects.create( + prefix=IPNetwork('203.0.113.0/24'), + rir=RIR.objects.first() + ) + prefixes = ( + Prefix(prefix=IPNetwork('203.0.113.0/26'), tenant=tenants[0]), + Prefix(prefix=IPNetwork('203.0.113.64/26'), tenant=tenants[1]), + ) + Prefix.objects.bulk_create(prefixes) + + url = reverse('ipam:aggregate_prefixes', kwargs={'pk': aggregate.pk}) + response = self.client.get(url, {'tenant_id': tenants[0].pk}) + + self.assertHttpStatus(response, 200) + self.assertEqual(len(response.context['table'].data), 1) + self.assertContains(response, '203.0.113.0/26') + self.assertNotContains(response, '203.0.113.64/26') + + def test_aggregate_prefixes_saved_filter(self): + self.add_permissions('ipam.view_aggregate', 'ipam.view_prefix') + + tenants = ( + Tenant(name='Aggregate Saved Tenant 1', slug='aggregate-saved-tenant-1'), + Tenant(name='Aggregate Saved Tenant 2', slug='aggregate-saved-tenant-2'), + ) + Tenant.objects.bulk_create(tenants) + + aggregate = Aggregate.objects.create( + prefix=IPNetwork('203.0.114.0/24'), + rir=RIR.objects.first() + ) + prefixes = ( + Prefix(prefix=IPNetwork('203.0.114.0/26'), tenant=tenants[0]), + Prefix(prefix=IPNetwork('203.0.114.64/26'), tenant=tenants[1]), + ) + Prefix.objects.bulk_create(prefixes) + + saved_filter = SavedFilter.objects.create( + name='Aggregate Tenant 1 prefixes', + slug='aggregate-tenant-1-prefixes', + parameters={ + 'tenant_id': [str(tenants[0].pk)], + }, + ) + saved_filter.object_types.add(ObjectType.objects.get_for_model(Prefix)) + + url = reverse('ipam:aggregate_prefixes', kwargs={'pk': aggregate.pk}) + response = self.client.get(url, {'filter_id': saved_filter.pk}) + + self.assertHttpStatus(response, 200) + self.assertEqual(len(response.context['table'].data), 1) + self.assertContains(response, '203.0.114.0/26') + self.assertNotContains(response, '203.0.114.64/26') + + def test_children_are_filtered_fallback(self): + """_children_are_filtered() rebuilds the queryset when prep_table_data() has not cached a result.""" + self.add_permissions('ipam.view_aggregate', 'ipam.view_prefix') + + aggregate = Aggregate.objects.create( + prefix=IPNetwork('203.0.115.0/24'), + rir=RIR.objects.first() + ) + tenant = Tenant.objects.create(name='Aggregate Fallback Tenant', slug='aggregate-fallback-tenant') + Prefix.objects.create(prefix=IPNetwork('203.0.115.0/26'), tenant=tenant) + Prefix.objects.create(prefix=IPNetwork('203.0.115.64/26')) + + # No cached value: the fallback path rebuilds the filtered queryset and detects the filter. + view = AggregatePrefixesView() + request = RequestFactory().get('/', {'tenant_id': tenant.pk}) + request.user = self.user + self.assertFalse(hasattr(view, '_child_queryset_is_filtered')) + self.assertTrue(view._children_are_filtered(request, aggregate)) + + # No cached value and no filter: the fallback path reports no filtering. + view = AggregatePrefixesView() + request = RequestFactory().get('/') + request.user = self.user + self.assertFalse(view._children_are_filtered(request, aggregate)) + + # A cached value takes precedence over the actual request state. + view = AggregatePrefixesView() + view._set_children_filtered(False) + request = RequestFactory().get('/', {'tenant_id': tenant.pk}) + request.user = self.user + self.assertFalse(view._children_are_filtered(request, aggregate)) + class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = Role @@ -588,6 +686,63 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase): url = reverse('ipam:prefix_prefixes', kwargs={'pk': prefixes[0].pk}) self.assertHttpStatus(self.client.get(url), 200) + def test_prefix_prefixes_filter_suppresses_available_prefixes(self): + self.add_permissions('ipam.view_prefix') + + tenants = ( + Tenant(name='Prefix Tenant 1', slug='prefix-tenant-1'), + Tenant(name='Prefix Tenant 2', slug='prefix-tenant-2'), + ) + Tenant.objects.bulk_create(tenants) + + parent = Prefix.objects.create(prefix=IPNetwork('198.51.100.0/24')) + prefixes = ( + Prefix(prefix=IPNetwork('198.51.100.0/26'), tenant=tenants[0]), + Prefix(prefix=IPNetwork('198.51.100.64/26'), tenant=tenants[1]), + ) + Prefix.objects.bulk_create(prefixes) + + url = reverse('ipam:prefix_prefixes', kwargs={'pk': parent.pk}) + response = self.client.get(url, {'tenant_id': tenants[0].pk}) + + self.assertHttpStatus(response, 200) + self.assertEqual(len(response.context['table'].data), 1) + self.assertContains(response, '198.51.100.0/26') + self.assertNotContains(response, '198.51.100.64/26') + + def test_prefix_prefixes_saved_filter_suppresses_available_prefixes(self): + self.add_permissions('ipam.view_prefix') + + tenants = ( + Tenant(name='Prefix Saved Tenant 1', slug='prefix-saved-tenant-1'), + Tenant(name='Prefix Saved Tenant 2', slug='prefix-saved-tenant-2'), + ) + Tenant.objects.bulk_create(tenants) + + parent = Prefix.objects.create(prefix=IPNetwork('198.51.101.0/24')) + prefixes = ( + Prefix(prefix=IPNetwork('198.51.101.0/26'), tenant=tenants[0]), + Prefix(prefix=IPNetwork('198.51.101.64/26'), tenant=tenants[1]), + ) + Prefix.objects.bulk_create(prefixes) + + saved_filter = SavedFilter.objects.create( + name='Prefix Tenant 1 prefixes', + slug='prefix-tenant-1-prefixes', + parameters={ + 'tenant_id': [str(tenants[0].pk)], + }, + ) + saved_filter.object_types.add(ObjectType.objects.get_for_model(Prefix)) + + url = reverse('ipam:prefix_prefixes', kwargs={'pk': parent.pk}) + response = self.client.get(url, {'filter_id': saved_filter.pk}) + + self.assertHttpStatus(response, 200) + self.assertEqual(len(response.context['table'].data), 1) + self.assertContains(response, '198.51.101.0/26') + self.assertNotContains(response, '198.51.101.64/26') + def test_prefix_ipranges(self): self.add_permissions('ipam.view_prefix', 'ipam.view_iprange') prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16')) @@ -616,6 +771,89 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase): url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk}) self.assertHttpStatus(self.client.get(url), 200) + def test_prefix_ipaddresses_filter(self): + self.add_permissions('ipam.view_prefix', 'ipam.view_ipaddress', 'ipam.view_iprange') + + tenants = ( + Tenant(name='IP Address Tenant 1', slug='ip-address-tenant-1'), + Tenant(name='IP Address Tenant 2', slug='ip-address-tenant-2'), + ) + Tenant.objects.bulk_create(tenants) + + prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24')) + ip_addresses = ( + IPAddress(address=IPNetwork('192.0.2.1/24'), tenant=tenants[0]), + IPAddress(address=IPNetwork('192.0.2.2/24'), tenant=tenants[1]), + ) + IPAddress.objects.bulk_create(ip_addresses) + + url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk}) + response = self.client.get(url, {'tenant_id': tenants[0].pk}) + + self.assertHttpStatus(response, 200) + self.assertEqual(len(response.context['table'].data), 1) + self.assertContains(response, '192.0.2.1/24') + self.assertNotContains(response, '192.0.2.2/24') + + def test_prefix_ipaddresses_saved_filter(self): + self.add_permissions('ipam.view_prefix', 'ipam.view_ipaddress', 'ipam.view_iprange') + + tenants = ( + Tenant(name='Saved Filter Tenant 1', slug='saved-filter-tenant-1'), + Tenant(name='Saved Filter Tenant 2', slug='saved-filter-tenant-2'), + ) + Tenant.objects.bulk_create(tenants) + + prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24')) + ip_addresses = ( + IPAddress(address=IPNetwork('192.0.2.1/24'), tenant=tenants[0]), + IPAddress(address=IPNetwork('192.0.2.2/24'), tenant=tenants[1]), + ) + IPAddress.objects.bulk_create(ip_addresses) + + saved_filter = SavedFilter.objects.create( + name='Tenant 1 IP addresses', + slug='tenant-1-ip-addresses', + parameters={ + 'tenant_id': [str(tenants[0].pk)], + }, + ) + saved_filter.object_types.add(ObjectType.objects.get_for_model(IPAddress)) + + url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk}) + response = self.client.get(url, {'filter_id': saved_filter.pk}) + + self.assertHttpStatus(response, 200) + self.assertEqual(len(response.context['table'].data), 1) + self.assertContains(response, '192.0.2.1/24') + self.assertNotContains(response, '192.0.2.2/24') + + def test_prefix_ipaddresses_unfiltered_shows_available_space(self): + """An unfiltered IP Addresses tab injects synthetic available-space rows.""" + self.add_permissions('ipam.view_prefix', 'ipam.view_ipaddress', 'ipam.view_iprange') + + prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29')) + IPAddress.objects.create(address=IPNetwork('192.0.2.1/29')) + + url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk}) + response = self.client.get(url) + + self.assertHttpStatus(response, 200) + self.assertGreater(len(response.context['table'].data), 1) + + def test_prefix_prefixes_unfiltered_shows_available_prefixes(self): + """An unfiltered Child Prefixes tab injects synthetic available-prefix rows.""" + self.add_permissions('ipam.view_prefix') + + parent = Prefix.objects.create(prefix=IPNetwork('198.51.102.0/24')) + Prefix.objects.create(prefix=IPNetwork('198.51.102.0/26')) + + url = reverse('ipam:prefix_prefixes', kwargs={'pk': parent.pk}) + response = self.client.get(url) + + self.assertHttpStatus(response, 200) + self.assertGreater(len(response.context['table'].data), 1) + def test_prefix_ipaddresses_with_single_address_range(self): self.add_permissions('ipam.view_prefix', 'ipam.view_ipaddress', 'ipam.view_iprange') # The IP Addresses tab annotates child IP addresses alongside any @@ -1130,6 +1368,70 @@ class VLANGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase): 'description': 'New description', } + def test_vlans_filter_suppresses_available_vlans(self): + self.add_permissions('ipam.view_vlangroup', 'ipam.view_vlan') + + group = VLANGroup.objects.create( + name='Filtered VLAN Group', + slug='filtered-vlan-group' + ) + vlans = ( + VLAN(group=group, vid=100, name='VLAN100'), + VLAN(group=group, vid=200, name='VLAN200'), + ) + VLAN.objects.bulk_create(vlans) + + url = reverse('ipam:vlangroup_vlans', kwargs={'pk': group.pk}) + response = self.client.get(url, {'vid': 100}) + + self.assertHttpStatus(response, 200) + self.assertEqual(len(response.context['table'].data), 1) + self.assertContains(response, 'VLAN100') + self.assertNotContains(response, 'VLAN200') + + def test_vlans_saved_filter_suppresses_available_vlans(self): + self.add_permissions('ipam.view_vlangroup', 'ipam.view_vlan') + + group = VLANGroup.objects.create( + name='Saved Filter VLAN Group', + slug='saved-filter-vlan-group' + ) + vlans = ( + VLAN(group=group, vid=100, name='VLAN100'), + VLAN(group=group, vid=200, name='VLAN200'), + ) + VLAN.objects.bulk_create(vlans) + + saved_filter = SavedFilter.objects.create( + name='VLAN 100', + slug='vlan-100', + parameters={ + 'vid': ['100'], + }, + ) + saved_filter.object_types.add(ObjectType.objects.get_for_model(VLAN)) + + url = reverse('ipam:vlangroup_vlans', kwargs={'pk': group.pk}) + response = self.client.get(url, {'filter_id': saved_filter.pk}) + + self.assertHttpStatus(response, 200) + self.assertEqual(len(response.context['table'].data), 1) + self.assertContains(response, 'VLAN100') + self.assertNotContains(response, 'VLAN200') + + def test_vlans_unfiltered_shows_available_vlans(self): + """An unfiltered VLANs tab injects synthetic available-VLAN rows.""" + self.add_permissions('ipam.view_vlangroup', 'ipam.view_vlan') + + group = VLANGroup.objects.create(name='Unfiltered VLAN Group', slug='unfiltered-vlan-group') + VLAN.objects.create(group=group, vid=1, name='VLAN0001') + + url = reverse('ipam:vlangroup_vlans', kwargs={'pk': group.pk}) + response = self.client.get(url) + + self.assertHttpStatus(response, 200) + self.assertGreater(len(response.context['table'].data), 1) + class VLANTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = VLAN diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 41f5a1832..98560cc8b 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -547,8 +547,64 @@ class AggregateView(generic.ObjectView): ) +class ChildAvailabilityMixin: + """ + Mixin for ObjectChildrenView subclasses that render synthetic "available" rows + (available IP space, prefixes, or VLANs) and must suppress them when the child + queryset has been narrowed by a direct or saved filter. + """ + + @staticmethod + def _where_signature(queryset): + # query.where is Django-internal, but it is the closest signal for "narrowed by a filter". + return str(queryset.query.where) + + def _set_children_filtered(self, is_filtered): + self._child_queryset_is_filtered = is_filtered + return is_filtered + + def _queryset_is_filtered(self, request, queryset, parent): + """ + Return True if the filtered child queryset differs from the unfiltered one. + + Compares WHERE clauses rather than testing queryset.query.where for truthiness, + because child querysets are already scoped to their parent object and carry WHERE + clauses before any user filter is applied. The result is cached on the view instance + so get_extra_context() can reuse it without rebuilding the queryset. + """ + if self.filterset is None: + return self._set_children_filtered(False) + + unfiltered = self.get_children(request, parent) + + return self._set_children_filtered( + self._where_signature(queryset) != self._where_signature(unfiltered) + ) + + def _children_are_filtered(self, request, parent): + """ + Return whether child objects are filtered. + + In the normal ObjectChildrenView flow prep_table_data() runs first and caches the + result, so this returns the cached value. Fall back to rebuilding the queryset for + direct calls where prep_table_data() has not run. + """ + if hasattr(self, '_child_queryset_is_filtered'): + return self._child_queryset_is_filtered + + if self.filterset is None: + return self._set_children_filtered(False) + + unfiltered = self.get_children(request, parent) + filtered = self.filterset(request.GET, unfiltered, request=request).qs + + return self._set_children_filtered( + self._where_signature(filtered) != self._where_signature(unfiltered) + ) + + @register_model_view(Aggregate, 'prefixes') -class AggregatePrefixesView(generic.ObjectChildrenView): +class AggregatePrefixesView(ChildAvailabilityMixin, generic.ObjectChildrenView): queryset = Aggregate.objects.all() child_model = Prefix table = tables.PrefixTable @@ -572,13 +628,21 @@ class AggregatePrefixesView(generic.ObjectChildrenView): show_available = bool(request.GET.get('show_available', 'true') == 'true') show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true') + if self._queryset_is_filtered(request, queryset, parent): + show_available = False + return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned) def get_extra_context(self, request, instance): + show_available = ( + bool(request.GET.get('show_available', 'true') == 'true') and + not self._children_are_filtered(request, instance) + ) + return { 'bulk_querystring': f'within={instance.prefix}', 'first_available_prefix': instance.get_first_available_prefix(), - 'show_available': bool(request.GET.get('show_available', 'true') == 'true'), + 'show_available': show_available, 'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'), } @@ -770,7 +834,7 @@ class PrefixView(generic.ObjectView): @register_model_view(Prefix, 'prefixes') -class PrefixPrefixesView(generic.ObjectChildrenView): +class PrefixPrefixesView(ChildAvailabilityMixin, generic.ObjectChildrenView): queryset = Prefix.objects.all() child_model = Prefix table = tables.PrefixTable @@ -794,13 +858,21 @@ class PrefixPrefixesView(generic.ObjectChildrenView): show_available = bool(request.GET.get('show_available', 'true') == 'true') show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true') + if self._queryset_is_filtered(request, queryset, parent): + show_available = False + return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned) def get_extra_context(self, request, instance): + show_available = ( + bool(request.GET.get('show_available', 'true') == 'true') and + not self._children_are_filtered(request, instance) + ) + return { 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&within={instance.prefix}", 'first_available_prefix': instance.get_first_available_prefix(), - 'show_available': bool(request.GET.get('show_available', 'true') == 'true'), + 'show_available': show_available, 'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'), } @@ -833,7 +905,7 @@ class PrefixIPRangesView(generic.ObjectChildrenView): @register_model_view(Prefix, 'ipaddresses', path='ip-addresses') -class PrefixIPAddressesView(generic.ObjectChildrenView): +class PrefixIPAddressesView(ChildAvailabilityMixin, generic.ObjectChildrenView): queryset = Prefix.objects.all() child_model = IPAddress table = tables.AnnotatedIPAddressTable @@ -851,9 +923,10 @@ class PrefixIPAddressesView(generic.ObjectChildrenView): return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group') def prep_table_data(self, request, queryset, parent): - if not request.GET.get('q') and not get_table_ordering(request, self.table): + if not self._queryset_is_filtered(request, queryset, parent) and not get_table_ordering(request, self.table): return annotate_ip_space(parent) - return queryset + + return super().prep_table_data(request, queryset, parent) def get_extra_context(self, request, instance): return { @@ -1292,7 +1365,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView): @register_model_view(VLANGroup, 'vlans') -class VLANGroupVLANsView(generic.ObjectChildrenView): +class VLANGroupVLANsView(ChildAvailabilityMixin, generic.ObjectChildrenView): queryset = VLANGroup.objects.all() child_model = VLAN table = tables.VLANTable @@ -1312,9 +1385,11 @@ class VLANGroupVLANsView(generic.ObjectChildrenView): ) def prep_table_data(self, request, queryset, parent): - if not get_table_ordering(request, self.table): + # Skip synthetic available rows under active filters: filtered-out VLANs would otherwise look available. + if not self._queryset_is_filtered(request, queryset, parent) and not get_table_ordering(request, self.table): return add_available_vlans(queryset, parent) - return queryset + + return super().prep_table_data(request, queryset, parent) # From 5561deb1e4cb55114a138d0d5058ab534195c76c Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Tue, 2 Jun 2026 19:56:22 +0200 Subject: [PATCH 09/58] fix(dcim): Refresh cable path for endpoints loaded before tracing Repair stale `_path` references when an endpoint instance is cabled but has no path set, as occurs during cable creation before path tracing. The `path` accessor now refreshes the denormalized FK from the database in this case, ensuring event payloads include connected endpoints. Fixes #21338 --- netbox/dcim/models/device_components.py | 23 ++++++++-- netbox/dcim/tests/test_models.py | 56 +++++++++++++++++++++++++ netbox/extras/tests/test_event_rules.py | 48 +++++++++++++++++++-- netbox/wireless/tests/test_signals.py | 41 ++++++++++++++++-- 4 files changed, 158 insertions(+), 10 deletions(-) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index cf80b94c4..717bb5de4 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -384,12 +384,27 @@ class PathEndpoint(models.Model): a stale in-memory `_path` relation while the database already points to a different CablePath (or to no path at all). - If the cached relation points to a CablePath that has just been - deleted, refresh only the `_path` field from the database and retry. - This keeps the fix cheap and narrowly scoped to the denormalized FK. + Two stale cases are repaired by refreshing only the `_path` field + from the database: + + 1. The endpoint is linked (by cable or wireless link) but `_path` is + unset, because the instance was loaded before its path was traced + (e.g. while queued for event serialization during link creation). + 2. The cached relation points to a CablePath row that has just been + deleted. + + Repairing case 1 costs one query per access for a linked endpoint + whose path is genuinely absent in the database. That state is + transient outside of tracing failures, so no result caching is + attempted here. """ if self._path_id is None: - return None + has_link = self.cable_id is not None or getattr(self, 'wireless_link_id', None) is not None + if self.pk and has_link: + self.refresh_from_db(fields=['_path']) + + if self._path_id is None: + return None try: return self._path diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index b266e9cde..8c2c6a2d2 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -1,4 +1,5 @@ from django.core.exceptions import ValidationError +from django.db.models.signals import post_save from django.test import TestCase, tag from circuits.models import * @@ -2132,6 +2133,61 @@ class CableTestCase(TestCase): self.assertIsNone(data['connected_endpoints_type']) self.assertFalse(data['connected_endpoints_reachable']) + @tag('regression') # #21338 + def test_path_refreshes_unset_cablepath_reference(self): + """ + An endpoint instance saved during cable creation, before path tracing, + should resolve its path and connected endpoints. + + The stale-instance preconditions rely on Cable.save() saving each + CableTermination (which re-saves the endpoint) before trace_paths + creates the CablePath records. + """ + device = Device.objects.get(name='TestDevice2') + interface_a = Interface.objects.create(device=device, name='eth2') + interface_b = Interface.objects.create(device=device, name='eth3') + + # Capture the instances handed to the event machinery on save + saved_instances = [] + + def capture(sender, instance, **kwargs): + saved_instances.append(instance) + + post_save.connect(capture, sender=Interface) + try: + Cable(a_terminations=[interface_a], b_terminations=[interface_b]).save() + finally: + post_save.disconnect(capture, sender=Interface) + + self.assertEqual(len(saved_instances), 2) + captured_a = next(i for i in saved_instances if i.pk == interface_a.pk) + captured_b = next(i for i in saved_instances if i.pk == interface_b.pk) + + # The captured instances predate path tracing: cabled, but no path yet + self.assertIsNotNone(captured_a.cable_id) + self.assertIsNone(captured_a._path_id) + self.assertIsNone(captured_b._path_id) + + # The accessor must repair the unset denormalized reference + self.assertIsNotNone(captured_a.path) + self.assertEqual(captured_a.connected_endpoints, [interface_b]) + + # Serialization as performed by the event queue must see the peer + data = serialize_for_event(captured_b) + self.assertEqual([endpoint['id'] for endpoint in data['connected_endpoints']], [interface_a.pk]) + self.assertEqual([peer['id'] for peer in data['link_peers']], [interface_a.pk]) + self.assertTrue(data['connected_endpoints_reachable']) + + def test_path_returns_none_for_unsaved_endpoint(self): + """ + An unsaved endpoint with a link assigned should report no path rather + than attempting a database refresh. + """ + device = Device.objects.get(name='TestDevice1') + cable = Cable.objects.first() + interface = Interface(device=device, name='tmp', cable=cable) + self.assertIsNone(interface.path) + class VirtualDeviceContextTestCase(TestCase): diff --git a/netbox/extras/tests/test_event_rules.py b/netbox/extras/tests/test_event_rules.py index ddd1b05b8..8bf21d175 100644 --- a/netbox/extras/tests/test_event_rules.py +++ b/netbox/extras/tests/test_event_rules.py @@ -6,7 +6,7 @@ from unittest.mock import Mock, patch import django_rq from django.conf import settings from django.http import HttpResponse -from django.test import RequestFactory +from django.test import RequestFactory, tag from django.urls import reverse from requests import Session from rest_framework import status @@ -14,14 +14,14 @@ from rest_framework import status from core.events import * from core.models import ObjectType from dcim.choices import SiteStatusChoices -from dcim.models import Site +from dcim.models import Interface, Site from extras.choices import EventRuleActionChoices from extras.events import enqueue_event, flush_events, serialize_for_event from extras.models import EventRule, Script, Tag, Webhook from extras.signals import process_job_end_event_rules from extras.webhooks import generate_signature, send_webhook from netbox.context_managers import event_tracking -from utilities.testing import APITestCase +from utilities.testing import APITestCase, create_test_device class EventRuleTestCase(APITestCase): @@ -531,6 +531,48 @@ class EventRuleTestCase(APITestCase): self.assertEqual(event['data']['name'], 'Site 1') self.assertIsNone(event['snapshots']['postchange']) + @tag('regression') # #21338 + def test_cable_creation_event_payload_includes_connected_endpoints(self): + """ + Interface update events queued during cable creation must include the + peer interface in connected_endpoints and link_peers. + """ + webhook = Webhook.objects.get(name='Webhook 1') + event_rule = EventRule.objects.create( + name='Interface Update Rule', + event_types=[OBJECT_UPDATED], + action_type=EventRuleActionChoices.WEBHOOK, + action_object_type=ObjectType.objects.get_for_model(Webhook), + action_object_id=webhook.id, + ) + event_rule.object_types.set([ObjectType.objects.get_for_model(Interface)]) + + device = create_test_device('Device 1') + interface_a = Interface.objects.create(device=device, name='eth0') + interface_b = Interface.objects.create(device=device, name='eth1') + + # Create a cable between the two interfaces via the REST API + data = { + 'a_terminations': [{'object_type': 'dcim.interface', 'object_id': interface_a.pk}], + 'b_terminations': [{'object_type': 'dcim.interface', 'object_id': interface_b.pk}], + } + url = reverse('dcim-api:cable-list') + self.add_permissions('dcim.add_cable') + response = self.client.post(url, data, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_201_CREATED) + + # One update event was queued for each interface + self.assertEqual(self.queue.count, 2) + payloads = {job.kwargs['data']['id']: job.kwargs['data'] for job in self.queue.jobs} + peers = {interface_a.pk: interface_b.pk, interface_b.pk: interface_a.pk} + self.assertEqual(set(payloads), set(peers)) + for interface_id, payload in payloads.items(): + peer_id = peers[interface_id] + self.assertIsNotNone(payload['connected_endpoints']) + self.assertEqual([endpoint['id'] for endpoint in payload['connected_endpoints']], [peer_id]) + self.assertEqual([peer['id'] for peer in payload['link_peers']], [peer_id]) + self.assertTrue(payload['connected_endpoints_reachable']) + def test_duplicate_triggers(self): """ Test for erroneous duplicate event triggers resulting from saving an object multiple times diff --git a/netbox/wireless/tests/test_signals.py b/netbox/wireless/tests/test_signals.py index 325b5c30d..53694e6d2 100644 --- a/netbox/wireless/tests/test_signals.py +++ b/netbox/wireless/tests/test_signals.py @@ -1,7 +1,8 @@ from types import SimpleNamespace from unittest.mock import MagicMock, patch -from django.test import SimpleTestCase, TestCase +from django.db.models.signals import post_save +from django.test import SimpleTestCase, TestCase, tag from dcim.choices import InterfaceTypeChoices from dcim.models import CablePath, Interface @@ -20,7 +21,7 @@ class WirelessLinkSignalTestCase(TestCase): @classmethod def setUpTestData(cls): cls.device = create_test_device('Device 1') - # Eight interfaces — one distinct pair per test method so no test sees stale + # Ten interfaces — one distinct pair per test method so no test sees stale # in-memory state mutated by a previous test. cls.interfaces = [ Interface.objects.create( @@ -31,7 +32,7 @@ class WirelessLinkSignalTestCase(TestCase): rf_channel_frequency=5160, rf_channel_width=20, ) - for i in range(8) + for i in range(10) ] def test_creating_link_assigns_wireless_link_to_both_interfaces(self): @@ -77,6 +78,40 @@ class WirelessLinkSignalTestCase(TestCase): # All wireless cable paths should be gone. self.assertEqual(CablePath.objects.count(), 0) + @tag('regression') # #21338 + def test_path_refreshes_unset_cablepath_reference(self): + """ + An interface instance saved during wireless link creation, before path + tracing, should resolve its path and connected endpoints. + + The stale-instance preconditions rely on update_connected_interfaces + saving both interfaces before creating cable paths. + """ + interface_a, interface_b = self.interfaces[8], self.interfaces[9] + + # Capture the instances handed to the event machinery on save + saved_instances = [] + + def capture(sender, instance, **kwargs): + saved_instances.append(instance) + + post_save.connect(capture, sender=Interface) + try: + WirelessLink(interface_a=interface_a, interface_b=interface_b, ssid='LINK1').save() + finally: + post_save.disconnect(capture, sender=Interface) + + self.assertEqual(len(saved_instances), 2) + captured_a = next(i for i in saved_instances if i.pk == interface_a.pk) + + # The captured instance predates path tracing: linked, but no path yet + self.assertIsNotNone(captured_a.wireless_link_id) + self.assertIsNone(captured_a._path_id) + + # The accessor must repair the unset denormalized reference + self.assertIsNotNone(captured_a.path) + self.assertEqual(captured_a.connected_endpoints, [interface_b]) + class UpdateConnectedInterfacesDirectHandlerTestCase(SimpleTestCase): """ From 208dd9b05bc6cc4da4a7bc446ba0529dfd3a661b Mon Sep 17 00:00:00 2001 From: Josh Niec Date: Tue, 2 Jun 2026 18:28:45 -0400 Subject: [PATCH 10/58] fix: address pr comments --- netbox/netbox/api/serializers/bulk.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/netbox/netbox/api/serializers/bulk.py b/netbox/netbox/api/serializers/bulk.py index 5817155e7..829d741c5 100644 --- a/netbox/netbox/api/serializers/bulk.py +++ b/netbox/netbox/api/serializers/bulk.py @@ -1,3 +1,4 @@ +import copy from functools import lru_cache from rest_framework import serializers @@ -11,10 +12,25 @@ __all__ = ( ) -class BulkPartialUpdateSchemaMixin: +class BulkOperationSerializer(ChangeLogMessageSerializer): + id = serializers.IntegerField() + + +class BulkUpdateSchemaMixin: + def get_fields(self): + fields = super().get_fields() + # Reuse the runtime bulk-operation ID field so the schema stays in sync + # with the validator that consumes `id` before model serialization. + _id = copy.deepcopy(BulkOperationSerializer().fields['id']) + _id.required = True + fields['id'] = _id + + return fields + + +class BulkPartialUpdateSchemaMixin(BulkUpdateSchemaMixin): def get_fields(self): fields = super().get_fields() - fields['id'] = serializers.IntegerField(required=True) for name, field in fields.items(): if name != 'id': @@ -33,6 +49,7 @@ def get_bulk_update_serializer_class(serializer_class, *, partial=False): normal model serializer. The runtime code consumes `id` before invoking the model serializer for each object. """ + meta = getattr(serializer_class, 'Meta') class Meta(meta): @@ -41,11 +58,10 @@ def get_bulk_update_serializer_class(serializer_class, *, partial=False): bases = ( (BulkPartialUpdateSchemaMixin, serializer_class) if partial - else (serializer_class,) + else (BulkUpdateSchemaMixin, serializer_class) ) attrs = { - 'id': serializers.IntegerField(required=True), 'Meta': Meta, '__module__': serializer_class.__module__, } @@ -54,5 +70,4 @@ def get_bulk_update_serializer_class(serializer_class, *, partial=False): return type(f'{prefix}{serializer_class.__name__}', bases, attrs) -class BulkOperationSerializer(ChangeLogMessageSerializer): - id = serializers.IntegerField() + From 1597f1bd7dc4e573b90af6ed0a087b565c4dd03d Mon Sep 17 00:00:00 2001 From: Josh Niec Date: Tue, 2 Jun 2026 18:30:10 -0400 Subject: [PATCH 11/58] fix: linting --- netbox/netbox/api/serializers/bulk.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/netbox/netbox/api/serializers/bulk.py b/netbox/netbox/api/serializers/bulk.py index 829d741c5..d6c1dda4b 100644 --- a/netbox/netbox/api/serializers/bulk.py +++ b/netbox/netbox/api/serializers/bulk.py @@ -68,6 +68,3 @@ def get_bulk_update_serializer_class(serializer_class, *, partial=False): prefix = 'PatchedBulk' if partial else 'Bulk' return type(f'{prefix}{serializer_class.__name__}', bases, attrs) - - - From c264b42abcf9b2d22b0d954e0dd24ca5f31692a5 Mon Sep 17 00:00:00 2001 From: Josh Niec Date: Tue, 2 Jun 2026 18:37:24 -0400 Subject: [PATCH 12/58] fix: avoid problem when fields is set to '__all__' --- netbox/netbox/api/serializers/bulk.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/api/serializers/bulk.py b/netbox/netbox/api/serializers/bulk.py index d6c1dda4b..1138eee9f 100644 --- a/netbox/netbox/api/serializers/bulk.py +++ b/netbox/netbox/api/serializers/bulk.py @@ -52,9 +52,17 @@ def get_bulk_update_serializer_class(serializer_class, *, partial=False): meta = getattr(serializer_class, 'Meta') - class Meta(meta): + if meta.fields == '__all__': + fields = '__all__' + else: fields = ('id', *[f for f in meta.fields if f != 'id']) + class Meta(meta): + pass + + # intentional; this is different than setting fields = fields within class Meta above + Meta.fields = fields + bases = ( (BulkPartialUpdateSchemaMixin, serializer_class) if partial From 56ac8030b81bfbfb8c100b9bfa610b7a7d17bd54 Mon Sep 17 00:00:00 2001 From: Josh Niec Date: Tue, 2 Jun 2026 19:48:37 -0400 Subject: [PATCH 13/58] fix: address pr comments --- netbox/netbox/api/serializers/bulk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/api/serializers/bulk.py b/netbox/netbox/api/serializers/bulk.py index 1138eee9f..706df0e97 100644 --- a/netbox/netbox/api/serializers/bulk.py +++ b/netbox/netbox/api/serializers/bulk.py @@ -7,6 +7,7 @@ from .features import ChangeLogMessageSerializer __all__ = ( 'BulkOperationSerializer', + 'BulkUpdateSchemaMixin', 'BulkPartialUpdateSchemaMixin', 'get_bulk_update_serializer_class' ) @@ -39,7 +40,7 @@ class BulkPartialUpdateSchemaMixin(BulkUpdateSchemaMixin): return fields -@lru_cache +@lru_cache(maxsize=None) def get_bulk_update_serializer_class(serializer_class, *, partial=False): """ Return a schema-only serializer for bulk PUT/PATCH requests. From c3d8b14a3d1332847f25fc6898f698304cffaa14 Mon Sep 17 00:00:00 2001 From: Josh Niec Date: Tue, 2 Jun 2026 19:51:42 -0400 Subject: [PATCH 14/58] fix: address pr comments --- netbox/netbox/api/serializers/bulk.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/api/serializers/bulk.py b/netbox/netbox/api/serializers/bulk.py index 706df0e97..fba4961b0 100644 --- a/netbox/netbox/api/serializers/bulk.py +++ b/netbox/netbox/api/serializers/bulk.py @@ -1,5 +1,5 @@ import copy -from functools import lru_cache +import functools from rest_framework import serializers @@ -7,8 +7,8 @@ from .features import ChangeLogMessageSerializer __all__ = ( 'BulkOperationSerializer', - 'BulkUpdateSchemaMixin', 'BulkPartialUpdateSchemaMixin', + 'BulkUpdateSchemaMixin', 'get_bulk_update_serializer_class' ) @@ -40,7 +40,7 @@ class BulkPartialUpdateSchemaMixin(BulkUpdateSchemaMixin): return fields -@lru_cache(maxsize=None) +@functools.cache def get_bulk_update_serializer_class(serializer_class, *, partial=False): """ Return a schema-only serializer for bulk PUT/PATCH requests. From 120700688ce294d07e565e347ffac8fdff56cbeb Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 06:34:32 +0000 Subject: [PATCH 15/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 432 +++++++++---------- 1 file changed, 216 insertions(+), 216 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 30fe42263..221179f88 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-02 06:30+0000\n" +"POT-Creation-Date: 2026-06-03 06:34+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -41,9 +41,9 @@ msgstr "" #: netbox/circuits/choices.py:21 netbox/dcim/choices.py:20 #: netbox/dcim/choices.py:102 netbox/dcim/choices.py:204 -#: netbox/dcim/choices.py:257 netbox/dcim/choices.py:1961 -#: netbox/dcim/choices.py:2019 netbox/dcim/choices.py:2086 -#: netbox/dcim/choices.py:2108 netbox/virtualization/choices.py:20 +#: netbox/dcim/choices.py:257 netbox/dcim/choices.py:1969 +#: netbox/dcim/choices.py:2027 netbox/dcim/choices.py:2094 +#: netbox/dcim/choices.py:2116 netbox/virtualization/choices.py:20 #: netbox/virtualization/choices.py:46 netbox/vpn/choices.py:18 #: netbox/vpn/choices.py:281 msgid "Planned" @@ -57,8 +57,8 @@ msgstr "" #: netbox/core/tables/tasks.py:23 netbox/dcim/choices.py:22 #: netbox/dcim/choices.py:103 netbox/dcim/choices.py:155 #: netbox/dcim/choices.py:203 netbox/dcim/choices.py:256 -#: netbox/dcim/choices.py:2018 netbox/dcim/choices.py:2085 -#: netbox/dcim/choices.py:2107 netbox/extras/tables/tables.py:648 +#: netbox/dcim/choices.py:2026 netbox/dcim/choices.py:2093 +#: netbox/dcim/choices.py:2115 netbox/extras/tables/tables.py:648 #: netbox/extras/ui/panels.py:441 netbox/ipam/choices.py:31 #: netbox/ipam/choices.py:49 netbox/ipam/choices.py:69 #: netbox/ipam/choices.py:154 netbox/users/forms/bulk_edit.py:41 @@ -69,8 +69,8 @@ msgid "Active" msgstr "" #: netbox/circuits/choices.py:24 netbox/dcim/choices.py:202 -#: netbox/dcim/choices.py:255 netbox/dcim/choices.py:2017 -#: netbox/dcim/choices.py:2087 netbox/dcim/choices.py:2106 +#: netbox/dcim/choices.py:255 netbox/dcim/choices.py:2025 +#: netbox/dcim/choices.py:2095 netbox/dcim/choices.py:2114 #: netbox/virtualization/choices.py:24 netbox/virtualization/choices.py:44 msgid "Offline" msgstr "" @@ -83,7 +83,7 @@ msgstr "" msgid "Decommissioned" msgstr "" -#: netbox/circuits/choices.py:90 netbox/dcim/choices.py:2030 +#: netbox/circuits/choices.py:90 netbox/dcim/choices.py:2038 #: netbox/dcim/tables/devices.py:1238 #: netbox/templates/dcim/interface/attrs/mac_address.html:3 #: netbox/tenancy/choices.py:17 @@ -456,7 +456,7 @@ msgstr "" #: netbox/dcim/tables/devicetypes.py:214 netbox/dcim/tables/devicetypes.py:255 #: netbox/dcim/tables/devicetypes.py:274 netbox/dcim/tables/racks.py:54 #: netbox/extras/forms/bulk_edit.py:313 netbox/extras/tables/tables.py:558 -#: netbox/netbox/ui/attrs.py:235 +#: netbox/netbox/ui/attrs.py:252 #: netbox/templates/extras/panels/customfieldchoiceset_choices.html:11 msgid "Color" msgstr "" @@ -1267,10 +1267,10 @@ msgstr "" #: netbox/dcim/models/device_component_templates.py:363 #: netbox/dcim/models/device_component_templates.py:606 #: netbox/dcim/models/device_component_templates.py:679 -#: netbox/dcim/models/device_components.py:648 -#: netbox/dcim/models/device_components.py:1231 -#: netbox/dcim/models/device_components.py:1279 -#: netbox/dcim/models/device_components.py:1521 +#: netbox/dcim/models/device_components.py:663 +#: netbox/dcim/models/device_components.py:1246 +#: netbox/dcim/models/device_components.py:1294 +#: netbox/dcim/models/device_components.py:1536 #: netbox/dcim/models/devices.py:395 netbox/dcim/models/racks.py:254 #: netbox/extras/models/tags.py:30 msgid "color" @@ -1297,8 +1297,8 @@ msgstr "" #: netbox/circuits/models/circuits.py:73 #: netbox/circuits/models/virtual_circuits.py:60 netbox/core/models/data.py:53 #: netbox/core/models/jobs.py:95 netbox/dcim/models/cables.py:93 -#: netbox/dcim/models/device_components.py:619 -#: netbox/dcim/models/device_components.py:1560 +#: netbox/dcim/models/device_components.py:634 +#: netbox/dcim/models/device_components.py:1575 #: netbox/dcim/models/devices.py:599 netbox/dcim/models/devices.py:1261 #: netbox/dcim/models/modules.py:264 netbox/dcim/models/power.py:95 #: netbox/dcim/models/racks.py:329 netbox/dcim/models/racks.py:716 @@ -1834,7 +1834,7 @@ msgstr "" #: netbox/core/choices.py:22 netbox/core/choices.py:59 #: netbox/core/constants.py:21 netbox/core/tables/tasks.py:35 #: netbox/dcim/choices.py:206 netbox/dcim/choices.py:259 -#: netbox/dcim/choices.py:2020 netbox/dcim/choices.py:2110 +#: netbox/dcim/choices.py:2028 netbox/dcim/choices.py:2118 #: netbox/virtualization/choices.py:48 msgid "Failed" msgstr "" @@ -2035,7 +2035,7 @@ msgid "User name" msgstr "" #: netbox/core/forms/bulk_edit.py:25 netbox/core/forms/filtersets.py:47 -#: netbox/core/tables/data.py:28 netbox/dcim/choices.py:2068 +#: netbox/core/tables/data.py:28 netbox/dcim/choices.py:2076 #: netbox/dcim/forms/bulk_edit.py:1151 netbox/dcim/forms/bulk_edit.py:1255 #: netbox/dcim/forms/bulk_edit.py:1278 netbox/dcim/forms/bulk_edit.py:1442 #: netbox/dcim/forms/filtersets.py:1687 netbox/dcim/forms/filtersets.py:1780 @@ -2216,7 +2216,7 @@ msgstr "" msgid "Rack Elevations" msgstr "" -#: netbox/core/forms/model_forms.py:160 netbox/dcim/choices.py:1939 +#: netbox/core/forms/model_forms.py:160 netbox/dcim/choices.py:1947 #: netbox/dcim/forms/bulk_edit.py:990 netbox/dcim/forms/bulk_edit.py:1396 #: netbox/dcim/forms/bulk_edit.py:1417 netbox/dcim/tables/racks.py:172 #: netbox/netbox/navigation/menu.py:324 netbox/netbox/navigation/menu.py:328 @@ -2373,13 +2373,13 @@ msgstr "" #: netbox/dcim/models/device_component_templates.py:447 #: netbox/dcim/models/device_component_templates.py:601 #: netbox/dcim/models/device_component_templates.py:674 -#: netbox/dcim/models/device_components.py:425 -#: netbox/dcim/models/device_components.py:452 -#: netbox/dcim/models/device_components.py:483 -#: netbox/dcim/models/device_components.py:625 -#: netbox/dcim/models/device_components.py:843 -#: netbox/dcim/models/device_components.py:1226 -#: netbox/dcim/models/device_components.py:1274 netbox/dcim/models/power.py:101 +#: netbox/dcim/models/device_components.py:440 +#: netbox/dcim/models/device_components.py:467 +#: netbox/dcim/models/device_components.py:498 +#: netbox/dcim/models/device_components.py:640 +#: netbox/dcim/models/device_components.py:858 +#: netbox/dcim/models/device_components.py:1241 +#: netbox/dcim/models/device_components.py:1289 netbox/dcim/models/power.py:101 #: netbox/extras/models/customfields.py:105 netbox/extras/models/search.py:42 #: netbox/virtualization/models/clusters.py:57 #: netbox/virtualization/models/virtualmachines.py:121 @@ -2397,9 +2397,9 @@ msgstr "" #: netbox/dcim/models/device_component_templates.py:452 #: netbox/dcim/models/device_component_templates.py:744 #: netbox/dcim/models/device_component_templates.py:779 -#: netbox/dcim/models/device_components.py:680 -#: netbox/dcim/models/device_components.py:1359 -#: netbox/dcim/models/device_components.py:1460 +#: netbox/dcim/models/device_components.py:695 +#: netbox/dcim/models/device_components.py:1374 +#: netbox/dcim/models/device_components.py:1475 #: netbox/extras/models/models.py:81 netbox/extras/models/models.py:323 #: netbox/extras/models/models.py:511 netbox/extras/models/models.py:593 #: netbox/users/models/permissions.py:32 netbox/users/models/tokens.py:65 @@ -2990,8 +2990,8 @@ msgid "Staging" msgstr "" #: netbox/dcim/choices.py:23 netbox/dcim/choices.py:208 -#: netbox/dcim/choices.py:260 netbox/dcim/choices.py:1962 -#: netbox/dcim/choices.py:2111 netbox/virtualization/choices.py:23 +#: netbox/dcim/choices.py:260 netbox/dcim/choices.py:1970 +#: netbox/dcim/choices.py:2119 netbox/virtualization/choices.py:23 #: netbox/virtualization/choices.py:49 netbox/vpn/choices.py:282 msgid "Decommissioning" msgstr "" @@ -3057,7 +3057,7 @@ msgstr "" msgid "Millimeters" msgstr "" -#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1984 +#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1992 msgid "Inches" msgstr "" @@ -3129,7 +3129,7 @@ msgid "Rear" msgstr "" #: netbox/dcim/choices.py:205 netbox/dcim/choices.py:258 -#: netbox/dcim/choices.py:2109 netbox/virtualization/choices.py:47 +#: netbox/dcim/choices.py:2117 netbox/virtualization/choices.py:47 msgid "Staged" msgstr "" @@ -3192,7 +3192,7 @@ msgstr "" #: netbox/dcim/choices.py:606 netbox/dcim/choices.py:853 #: netbox/dcim/choices.py:1501 netbox/dcim/choices.py:1503 -#: netbox/dcim/choices.py:1742 netbox/dcim/choices.py:1744 +#: netbox/dcim/choices.py:1750 netbox/dcim/choices.py:1752 #: netbox/netbox/navigation/menu.py:219 msgid "Other" msgstr "" @@ -3359,200 +3359,200 @@ msgstr "" msgid "Passive 48V (4-pair)" msgstr "" -#: netbox/dcim/choices.py:1670 +#: netbox/dcim/choices.py:1674 msgid "Copper" msgstr "" -#: netbox/dcim/choices.py:1693 +#: netbox/dcim/choices.py:1697 msgid "Fiber Optic" msgstr "" -#: netbox/dcim/choices.py:1729 netbox/dcim/choices.py:1945 +#: netbox/dcim/choices.py:1737 netbox/dcim/choices.py:1953 msgid "USB" msgstr "" -#: netbox/dcim/choices.py:1786 +#: netbox/dcim/choices.py:1794 msgid "Single" msgstr "" -#: netbox/dcim/choices.py:1788 +#: netbox/dcim/choices.py:1796 msgid "1C1P" msgstr "" -#: netbox/dcim/choices.py:1789 +#: netbox/dcim/choices.py:1797 msgid "1C2P" msgstr "" -#: netbox/dcim/choices.py:1790 +#: netbox/dcim/choices.py:1798 msgid "1C4P" msgstr "" -#: netbox/dcim/choices.py:1791 +#: netbox/dcim/choices.py:1799 msgid "1C6P" msgstr "" -#: netbox/dcim/choices.py:1792 +#: netbox/dcim/choices.py:1800 msgid "1C8P" msgstr "" -#: netbox/dcim/choices.py:1793 +#: netbox/dcim/choices.py:1801 msgid "1C12P" msgstr "" -#: netbox/dcim/choices.py:1794 +#: netbox/dcim/choices.py:1802 msgid "1C16P" msgstr "" -#: netbox/dcim/choices.py:1798 +#: netbox/dcim/choices.py:1806 msgid "Trunk" msgstr "" -#: netbox/dcim/choices.py:1800 +#: netbox/dcim/choices.py:1808 msgid "2C1P trunk" msgstr "" -#: netbox/dcim/choices.py:1801 +#: netbox/dcim/choices.py:1809 msgid "2C2P trunk" msgstr "" -#: netbox/dcim/choices.py:1802 +#: netbox/dcim/choices.py:1810 msgid "2C4P trunk" msgstr "" -#: netbox/dcim/choices.py:1803 +#: netbox/dcim/choices.py:1811 msgid "2C4P trunk (shuffle)" msgstr "" -#: netbox/dcim/choices.py:1804 +#: netbox/dcim/choices.py:1812 msgid "2C6P trunk" msgstr "" -#: netbox/dcim/choices.py:1805 +#: netbox/dcim/choices.py:1813 msgid "2C8P trunk" msgstr "" -#: netbox/dcim/choices.py:1806 +#: netbox/dcim/choices.py:1814 msgid "2C12P trunk" msgstr "" -#: netbox/dcim/choices.py:1807 +#: netbox/dcim/choices.py:1815 msgid "4C1P trunk" msgstr "" -#: netbox/dcim/choices.py:1808 +#: netbox/dcim/choices.py:1816 msgid "4C2P trunk" msgstr "" -#: netbox/dcim/choices.py:1809 +#: netbox/dcim/choices.py:1817 msgid "4C4P trunk" msgstr "" -#: netbox/dcim/choices.py:1810 +#: netbox/dcim/choices.py:1818 msgid "4C4P trunk (shuffle)" msgstr "" -#: netbox/dcim/choices.py:1811 +#: netbox/dcim/choices.py:1819 msgid "4C6P trunk" msgstr "" -#: netbox/dcim/choices.py:1812 +#: netbox/dcim/choices.py:1820 msgid "4C8P trunk" msgstr "" -#: netbox/dcim/choices.py:1813 +#: netbox/dcim/choices.py:1821 msgid "8C4P trunk" msgstr "" -#: netbox/dcim/choices.py:1817 +#: netbox/dcim/choices.py:1825 msgid "Breakout" msgstr "" -#: netbox/dcim/choices.py:1819 +#: netbox/dcim/choices.py:1827 msgid "1C2P:2C1P breakout" msgstr "" -#: netbox/dcim/choices.py:1820 +#: netbox/dcim/choices.py:1828 msgid "1C4P:4C1P breakout" msgstr "" -#: netbox/dcim/choices.py:1821 +#: netbox/dcim/choices.py:1829 msgid "1C6P:6C1P breakout" msgstr "" -#: netbox/dcim/choices.py:1822 +#: netbox/dcim/choices.py:1830 msgid "2C4P:8C1P breakout (shuffle)" msgstr "" -#: netbox/dcim/choices.py:1880 +#: netbox/dcim/choices.py:1888 msgid "Copper - Twisted Pair (UTP/STP)" msgstr "" -#: netbox/dcim/choices.py:1894 +#: netbox/dcim/choices.py:1902 msgid "Copper - Twinax (DAC)" msgstr "" -#: netbox/dcim/choices.py:1901 +#: netbox/dcim/choices.py:1909 msgid "Copper - Coaxial" msgstr "" -#: netbox/dcim/choices.py:1916 +#: netbox/dcim/choices.py:1924 msgid "Fiber - Multimode" msgstr "" -#: netbox/dcim/choices.py:1927 +#: netbox/dcim/choices.py:1935 msgid "Fiber - Single-mode" msgstr "" -#: netbox/dcim/choices.py:1935 +#: netbox/dcim/choices.py:1943 msgid "Fiber - Other" msgstr "" -#: netbox/dcim/choices.py:1960 netbox/dcim/forms/filtersets.py:1470 +#: netbox/dcim/choices.py:1968 netbox/dcim/forms/filtersets.py:1470 msgid "Connected" msgstr "" -#: netbox/dcim/choices.py:1979 netbox/netbox/choices.py:177 +#: netbox/dcim/choices.py:1987 netbox/netbox/choices.py:177 msgid "Kilometers" msgstr "" -#: netbox/dcim/choices.py:1980 netbox/netbox/choices.py:178 +#: netbox/dcim/choices.py:1988 netbox/netbox/choices.py:178 #: netbox/templates/dcim/cable_trace.html:65 msgid "Meters" msgstr "" -#: netbox/dcim/choices.py:1981 +#: netbox/dcim/choices.py:1989 msgid "Centimeters" msgstr "" -#: netbox/dcim/choices.py:1982 netbox/netbox/choices.py:179 +#: netbox/dcim/choices.py:1990 netbox/netbox/choices.py:179 msgid "Miles" msgstr "" -#: netbox/dcim/choices.py:1983 netbox/netbox/choices.py:180 +#: netbox/dcim/choices.py:1991 netbox/netbox/choices.py:180 #: netbox/templates/dcim/cable_trace.html:66 msgid "Feet" msgstr "" -#: netbox/dcim/choices.py:2031 +#: netbox/dcim/choices.py:2039 msgid "Redundant" msgstr "" -#: netbox/dcim/choices.py:2052 +#: netbox/dcim/choices.py:2060 msgid "Single phase" msgstr "" -#: netbox/dcim/choices.py:2053 +#: netbox/dcim/choices.py:2061 msgid "Three-phase" msgstr "" -#: netbox/dcim/choices.py:2069 netbox/extras/choices.py:53 +#: netbox/dcim/choices.py:2077 netbox/extras/choices.py:53 #: netbox/netbox/preferences.py:45 netbox/netbox/preferences.py:70 #: netbox/templates/extras/customfield/attrs/search_weight.html:1 #: netbox/vpn/choices.py:20 netbox/wireless/choices.py:27 msgid "Disabled" msgstr "" -#: netbox/dcim/choices.py:2070 +#: netbox/dcim/choices.py:2078 msgid "Faulty" msgstr "" @@ -4015,7 +4015,7 @@ msgstr "" #: netbox/dcim/filtersets.py:2158 netbox/dcim/forms/bulk_edit.py:1568 #: netbox/dcim/forms/bulk_import.py:1050 netbox/dcim/forms/filtersets.py:1755 #: netbox/dcim/forms/model_forms.py:1619 -#: netbox/dcim/models/device_components.py:942 +#: netbox/dcim/models/device_components.py:957 #: netbox/dcim/tables/devices.py:678 netbox/dcim/ui/panels.py:521 #: netbox/ipam/filtersets.py:372 netbox/ipam/filtersets.py:384 #: netbox/ipam/filtersets.py:523 netbox/ipam/filtersets.py:630 @@ -4072,7 +4072,7 @@ msgstr "" #: netbox/dcim/filtersets.py:2189 netbox/dcim/forms/filtersets.py:1726 #: netbox/dcim/forms/model_forms.py:1636 -#: netbox/dcim/models/device_components.py:743 +#: netbox/dcim/models/device_components.py:758 #: netbox/ipam/forms/filtersets.py:552 netbox/ipam/forms/model_forms.py:733 #: netbox/virtualization/forms/bulk_edit.py:277 #: netbox/virtualization/forms/filtersets.py:313 @@ -4636,7 +4636,7 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:1064 #: netbox/dcim/models/device_component_templates.py:302 -#: netbox/dcim/models/device_components.py:495 +#: netbox/dcim/models/device_components.py:510 msgid "Maximum power draw (watts)" msgstr "" @@ -4646,7 +4646,7 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:1070 #: netbox/dcim/models/device_component_templates.py:309 -#: netbox/dcim/models/device_components.py:502 +#: netbox/dcim/models/device_components.py:517 msgid "Allocated power draw (watts)" msgstr "" @@ -4669,7 +4669,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:1001 netbox/dcim/forms/filtersets.py:1711 #: netbox/dcim/forms/filtersets.py:1796 netbox/dcim/forms/object_import.py:91 #: netbox/dcim/models/device_component_templates.py:472 -#: netbox/dcim/models/device_components.py:914 netbox/dcim/ui/panels.py:494 +#: netbox/dcim/models/device_components.py:929 netbox/dcim/ui/panels.py:494 msgid "PoE mode" msgstr "" @@ -4677,7 +4677,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:1007 netbox/dcim/forms/filtersets.py:1716 #: netbox/dcim/forms/filtersets.py:1801 netbox/dcim/forms/object_import.py:96 #: netbox/dcim/models/device_component_templates.py:479 -#: netbox/dcim/models/device_components.py:921 netbox/dcim/ui/panels.py:495 +#: netbox/dcim/models/device_components.py:936 netbox/dcim/ui/panels.py:495 msgid "PoE type" msgstr "" @@ -5404,7 +5404,7 @@ msgstr "" msgid "IPv6 address with prefix length, e.g. 2001:db8::1/64" msgstr "" -#: netbox/dcim/forms/common.py:20 netbox/dcim/models/device_components.py:690 +#: netbox/dcim/forms/common.py:20 netbox/dcim/models/device_components.py:705 #: netbox/dcim/ui/panels.py:490 netbox/virtualization/forms/bulk_edit.py:236 #: netbox/virtualization/ui/panels.py:94 msgid "MTU" @@ -5535,7 +5535,7 @@ msgid "Mgmt only" msgstr "" #: netbox/dcim/forms/filtersets.py:1706 netbox/dcim/forms/model_forms.py:1631 -#: netbox/dcim/models/device_components.py:867 netbox/dcim/ui/panels.py:520 +#: netbox/dcim/models/device_components.py:882 netbox/dcim/ui/panels.py:520 msgid "WWN" msgstr "" @@ -5914,7 +5914,7 @@ msgstr "" #: netbox/dcim/forms/object_create.py:312 netbox/dcim/tables/devices.py:1160 #: netbox/ipam/tables/fhrp.py:31 netbox/ipam/ui/panels.py:185 -#: netbox/ipam/views.py:1508 netbox/templates/dcim/virtualchassis_edit.html:59 +#: netbox/ipam/views.py:1583 netbox/templates/dcim/virtualchassis_edit.html:59 #: netbox/users/views.py:374 msgid "Members" msgstr "" @@ -6124,12 +6124,12 @@ msgid "console server port templates" msgstr "" #: netbox/dcim/models/device_component_templates.py:298 -#: netbox/dcim/models/device_components.py:491 +#: netbox/dcim/models/device_components.py:506 msgid "maximum draw" msgstr "" #: netbox/dcim/models/device_component_templates.py:305 -#: netbox/dcim/models/device_components.py:498 +#: netbox/dcim/models/device_components.py:513 msgid "allocated draw" msgstr "" @@ -6142,18 +6142,18 @@ msgid "power port templates" msgstr "" #: netbox/dcim/models/device_component_templates.py:336 -#: netbox/dcim/models/device_components.py:518 +#: netbox/dcim/models/device_components.py:533 #, python-brace-format msgid "Allocated draw cannot exceed the maximum draw ({maximum_draw}W)." msgstr "" #: netbox/dcim/models/device_component_templates.py:374 -#: netbox/dcim/models/device_components.py:640 +#: netbox/dcim/models/device_components.py:655 msgid "feed leg" msgstr "" #: netbox/dcim/models/device_component_templates.py:379 -#: netbox/dcim/models/device_components.py:645 +#: netbox/dcim/models/device_components.py:660 msgid "Phase (for three-phase feeds)" msgstr "" @@ -6176,17 +6176,17 @@ msgid "Parent power port ({power_port}) must belong to the same module type" msgstr "" #: netbox/dcim/models/device_component_templates.py:457 -#: netbox/dcim/models/device_components.py:849 +#: netbox/dcim/models/device_components.py:864 msgid "management only" msgstr "" #: netbox/dcim/models/device_component_templates.py:465 -#: netbox/dcim/models/device_components.py:714 +#: netbox/dcim/models/device_components.py:729 msgid "bridge interface" msgstr "" #: netbox/dcim/models/device_component_templates.py:486 -#: netbox/dcim/models/device_components.py:875 +#: netbox/dcim/models/device_components.py:890 msgid "wireless role" msgstr "" @@ -6215,8 +6215,8 @@ msgstr "" #: netbox/dcim/models/device_component_templates.py:610 #: netbox/dcim/models/device_component_templates.py:683 -#: netbox/dcim/models/device_components.py:1235 -#: netbox/dcim/models/device_components.py:1283 +#: netbox/dcim/models/device_components.py:1250 +#: netbox/dcim/models/device_components.py:1298 msgid "positions" msgstr "" @@ -6251,12 +6251,12 @@ msgid "" msgstr "" #: netbox/dcim/models/device_component_templates.py:738 -#: netbox/dcim/models/device_components.py:1353 +#: netbox/dcim/models/device_components.py:1368 msgid "position" msgstr "" #: netbox/dcim/models/device_component_templates.py:741 -#: netbox/dcim/models/device_components.py:1356 +#: netbox/dcim/models/device_components.py:1371 msgid "Identifier to reference when renaming installed components" msgstr "" @@ -6284,12 +6284,12 @@ msgid "" msgstr "" #: netbox/dcim/models/device_component_templates.py:858 -#: netbox/dcim/models/device_components.py:1581 +#: netbox/dcim/models/device_components.py:1596 msgid "part ID" msgstr "" #: netbox/dcim/models/device_component_templates.py:860 -#: netbox/dcim/models/device_components.py:1583 +#: netbox/dcim/models/device_components.py:1598 msgid "Manufacturer-assigned part identifier" msgstr "" @@ -6350,82 +6350,82 @@ msgstr "" msgid "{class_name} models must declare a parent_object property" msgstr "" -#: netbox/dcim/models/device_components.py:430 -#: netbox/dcim/models/device_components.py:457 -#: netbox/dcim/models/device_components.py:488 -#: netbox/dcim/models/device_components.py:630 +#: netbox/dcim/models/device_components.py:445 +#: netbox/dcim/models/device_components.py:472 +#: netbox/dcim/models/device_components.py:503 +#: netbox/dcim/models/device_components.py:645 msgid "Physical port type" msgstr "" -#: netbox/dcim/models/device_components.py:433 -#: netbox/dcim/models/device_components.py:460 +#: netbox/dcim/models/device_components.py:448 +#: netbox/dcim/models/device_components.py:475 msgid "speed" msgstr "" -#: netbox/dcim/models/device_components.py:437 -#: netbox/dcim/models/device_components.py:464 +#: netbox/dcim/models/device_components.py:452 +#: netbox/dcim/models/device_components.py:479 msgid "Port speed in bits per second" msgstr "" -#: netbox/dcim/models/device_components.py:443 +#: netbox/dcim/models/device_components.py:458 msgid "console port" msgstr "" -#: netbox/dcim/models/device_components.py:444 +#: netbox/dcim/models/device_components.py:459 msgid "console ports" msgstr "" -#: netbox/dcim/models/device_components.py:470 +#: netbox/dcim/models/device_components.py:485 msgid "console server port" msgstr "" -#: netbox/dcim/models/device_components.py:471 +#: netbox/dcim/models/device_components.py:486 msgid "console server ports" msgstr "" -#: netbox/dcim/models/device_components.py:508 +#: netbox/dcim/models/device_components.py:523 msgid "power port" msgstr "" -#: netbox/dcim/models/device_components.py:509 +#: netbox/dcim/models/device_components.py:524 msgid "power ports" msgstr "" -#: netbox/dcim/models/device_components.py:655 +#: netbox/dcim/models/device_components.py:670 msgid "power outlet" msgstr "" -#: netbox/dcim/models/device_components.py:656 +#: netbox/dcim/models/device_components.py:671 msgid "power outlets" msgstr "" -#: netbox/dcim/models/device_components.py:664 +#: netbox/dcim/models/device_components.py:679 #, python-brace-format msgid "Parent power port ({power_port}) must belong to the same device" msgstr "" -#: netbox/dcim/models/device_components.py:693 netbox/vpn/models/crypto.py:80 +#: netbox/dcim/models/device_components.py:708 netbox/vpn/models/crypto.py:80 #: netbox/vpn/models/crypto.py:222 msgid "mode" msgstr "" -#: netbox/dcim/models/device_components.py:698 +#: netbox/dcim/models/device_components.py:713 msgid "IEEE 802.1Q tagging strategy" msgstr "" -#: netbox/dcim/models/device_components.py:706 +#: netbox/dcim/models/device_components.py:721 msgid "parent interface" msgstr "" -#: netbox/dcim/models/device_components.py:722 +#: netbox/dcim/models/device_components.py:737 msgid "untagged VLAN" msgstr "" -#: netbox/dcim/models/device_components.py:728 +#: netbox/dcim/models/device_components.py:743 msgid "tagged VLANs" msgstr "" -#: netbox/dcim/models/device_components.py:736 +#: netbox/dcim/models/device_components.py:751 #: netbox/dcim/tables/devices.py:636 netbox/dcim/ui/panels.py:497 #: netbox/ipam/forms/bulk_edit.py:456 netbox/ipam/forms/bulk_import.py:554 #: netbox/ipam/forms/filtersets.py:629 netbox/ipam/forms/model_forms.py:714 @@ -6434,299 +6434,299 @@ msgstr "" msgid "Q-in-Q SVLAN" msgstr "" -#: netbox/dcim/models/device_components.py:751 +#: netbox/dcim/models/device_components.py:766 msgid "primary MAC address" msgstr "" -#: netbox/dcim/models/device_components.py:763 +#: netbox/dcim/models/device_components.py:778 msgid "Only Q-in-Q interfaces may specify a service VLAN." msgstr "" -#: netbox/dcim/models/device_components.py:774 +#: netbox/dcim/models/device_components.py:789 #, python-brace-format msgid "" "MAC address {mac_address} is assigned to a different interface ({interface})." msgstr "" -#: netbox/dcim/models/device_components.py:840 +#: netbox/dcim/models/device_components.py:855 msgid "parent LAG" msgstr "" -#: netbox/dcim/models/device_components.py:850 +#: netbox/dcim/models/device_components.py:865 msgid "This interface is used only for out-of-band management" msgstr "" -#: netbox/dcim/models/device_components.py:855 +#: netbox/dcim/models/device_components.py:870 msgid "speed (Kbps)" msgstr "" -#: netbox/dcim/models/device_components.py:858 +#: netbox/dcim/models/device_components.py:873 msgid "duplex" msgstr "" -#: netbox/dcim/models/device_components.py:868 +#: netbox/dcim/models/device_components.py:883 msgid "64-bit World Wide Name" msgstr "" -#: netbox/dcim/models/device_components.py:882 +#: netbox/dcim/models/device_components.py:897 msgid "wireless channel" msgstr "" -#: netbox/dcim/models/device_components.py:889 +#: netbox/dcim/models/device_components.py:904 msgid "channel frequency (MHz)" msgstr "" -#: netbox/dcim/models/device_components.py:890 -#: netbox/dcim/models/device_components.py:898 +#: netbox/dcim/models/device_components.py:905 +#: netbox/dcim/models/device_components.py:913 msgid "Populated by selected channel (if set)" msgstr "" -#: netbox/dcim/models/device_components.py:907 +#: netbox/dcim/models/device_components.py:922 msgid "transmit power (dBm)" msgstr "" -#: netbox/dcim/models/device_components.py:934 netbox/wireless/models.py:125 +#: netbox/dcim/models/device_components.py:949 netbox/wireless/models.py:125 msgid "wireless LANs" msgstr "" -#: netbox/dcim/models/device_components.py:982 +#: netbox/dcim/models/device_components.py:997 #: netbox/virtualization/models/virtualmachines.py:515 msgid "interface" msgstr "" -#: netbox/dcim/models/device_components.py:983 +#: netbox/dcim/models/device_components.py:998 #: netbox/virtualization/models/virtualmachines.py:516 msgid "interfaces" msgstr "" -#: netbox/dcim/models/device_components.py:991 +#: netbox/dcim/models/device_components.py:1006 #, python-brace-format msgid "{display_type} interfaces cannot have a cable attached." msgstr "" -#: netbox/dcim/models/device_components.py:999 +#: netbox/dcim/models/device_components.py:1014 #, python-brace-format msgid "{display_type} interfaces cannot be marked as connected." msgstr "" -#: netbox/dcim/models/device_components.py:1008 +#: netbox/dcim/models/device_components.py:1023 #: netbox/virtualization/models/virtualmachines.py:526 msgid "An interface cannot be its own parent." msgstr "" -#: netbox/dcim/models/device_components.py:1012 +#: netbox/dcim/models/device_components.py:1027 msgid "Only virtual interfaces may be assigned to a parent interface." msgstr "" -#: netbox/dcim/models/device_components.py:1019 +#: netbox/dcim/models/device_components.py:1034 #, python-brace-format msgid "" "The selected parent interface ({interface}) belongs to a different device " "({device})" msgstr "" -#: netbox/dcim/models/device_components.py:1025 +#: netbox/dcim/models/device_components.py:1040 #, python-brace-format msgid "" "The selected parent interface ({interface}) belongs to {device}, which is " "not part of virtual chassis {virtual_chassis}." msgstr "" -#: netbox/dcim/models/device_components.py:1041 +#: netbox/dcim/models/device_components.py:1056 #, python-brace-format msgid "" "The selected bridge interface ({bridge}) belongs to a different device " "({device})." msgstr "" -#: netbox/dcim/models/device_components.py:1047 +#: netbox/dcim/models/device_components.py:1062 #, python-brace-format msgid "" "The selected bridge interface ({interface}) belongs to {device}, which is " "not part of virtual chassis {virtual_chassis}." msgstr "" -#: netbox/dcim/models/device_components.py:1058 +#: netbox/dcim/models/device_components.py:1073 msgid "Virtual interfaces cannot have a parent LAG interface." msgstr "" -#: netbox/dcim/models/device_components.py:1062 +#: netbox/dcim/models/device_components.py:1077 msgid "A LAG interface cannot be its own parent." msgstr "" -#: netbox/dcim/models/device_components.py:1069 +#: netbox/dcim/models/device_components.py:1084 #, python-brace-format msgid "" "The selected LAG interface ({lag}) belongs to a different device ({device})." msgstr "" -#: netbox/dcim/models/device_components.py:1075 +#: netbox/dcim/models/device_components.py:1090 #, python-brace-format msgid "" "The selected LAG interface ({lag}) belongs to {device}, which is not part of " "virtual chassis {virtual_chassis}." msgstr "" -#: netbox/dcim/models/device_components.py:1085 +#: netbox/dcim/models/device_components.py:1100 msgid "Channel may be set only on wireless interfaces." msgstr "" -#: netbox/dcim/models/device_components.py:1091 +#: netbox/dcim/models/device_components.py:1106 msgid "Channel frequency may be set only on wireless interfaces." msgstr "" -#: netbox/dcim/models/device_components.py:1095 +#: netbox/dcim/models/device_components.py:1110 msgid "Cannot specify custom frequency with channel selected." msgstr "" -#: netbox/dcim/models/device_components.py:1101 +#: netbox/dcim/models/device_components.py:1116 msgid "Channel width may be set only on wireless interfaces." msgstr "" -#: netbox/dcim/models/device_components.py:1103 +#: netbox/dcim/models/device_components.py:1118 msgid "Cannot specify custom width with channel selected." msgstr "" -#: netbox/dcim/models/device_components.py:1107 +#: netbox/dcim/models/device_components.py:1122 msgid "Interface mode does not support an untagged vlan." msgstr "" -#: netbox/dcim/models/device_components.py:1113 +#: netbox/dcim/models/device_components.py:1128 #, python-brace-format msgid "" "The untagged VLAN ({untagged_vlan}) must belong to the same site as the " "interface's parent device, or it must be global." msgstr "" -#: netbox/dcim/models/device_components.py:1210 +#: netbox/dcim/models/device_components.py:1225 #, python-brace-format msgid "Rear port ({rear_port}) must belong to the same device" msgstr "" -#: netbox/dcim/models/device_components.py:1252 +#: netbox/dcim/models/device_components.py:1267 msgid "front port" msgstr "" -#: netbox/dcim/models/device_components.py:1253 +#: netbox/dcim/models/device_components.py:1268 msgid "front ports" msgstr "" -#: netbox/dcim/models/device_components.py:1264 +#: netbox/dcim/models/device_components.py:1279 #, python-brace-format msgid "" "The number of positions cannot be less than the number of mapped rear ports " "({count})" msgstr "" -#: netbox/dcim/models/device_components.py:1294 +#: netbox/dcim/models/device_components.py:1309 msgid "rear port" msgstr "" -#: netbox/dcim/models/device_components.py:1295 +#: netbox/dcim/models/device_components.py:1310 msgid "rear ports" msgstr "" -#: netbox/dcim/models/device_components.py:1306 +#: netbox/dcim/models/device_components.py:1321 #, python-brace-format msgid "" "The number of positions cannot be less than the number of mapped front ports " "({count})" msgstr "" -#: netbox/dcim/models/device_components.py:1380 +#: netbox/dcim/models/device_components.py:1395 msgid "module bay" msgstr "" -#: netbox/dcim/models/device_components.py:1381 +#: netbox/dcim/models/device_components.py:1396 msgid "module bays" msgstr "" -#: netbox/dcim/models/device_components.py:1398 +#: netbox/dcim/models/device_components.py:1413 #: netbox/dcim/models/modules.py:321 msgid "A module bay cannot belong to a module installed within it." msgstr "" -#: netbox/dcim/models/device_components.py:1467 +#: netbox/dcim/models/device_components.py:1482 msgid "device bay" msgstr "" -#: netbox/dcim/models/device_components.py:1468 +#: netbox/dcim/models/device_components.py:1483 msgid "device bays" msgstr "" -#: netbox/dcim/models/device_components.py:1475 +#: netbox/dcim/models/device_components.py:1490 #, python-brace-format msgid "This type of device ({device_type}) does not support device bays." msgstr "" -#: netbox/dcim/models/device_components.py:1486 +#: netbox/dcim/models/device_components.py:1501 msgid "Cannot install a device in a disabled device bay." msgstr "" -#: netbox/dcim/models/device_components.py:1491 +#: netbox/dcim/models/device_components.py:1506 msgid "Cannot install a device into itself." msgstr "" -#: netbox/dcim/models/device_components.py:1499 +#: netbox/dcim/models/device_components.py:1514 #, python-brace-format msgid "" "Cannot install the specified device; device is already installed in {bay}." msgstr "" -#: netbox/dcim/models/device_components.py:1527 +#: netbox/dcim/models/device_components.py:1542 msgid "inventory item role" msgstr "" -#: netbox/dcim/models/device_components.py:1528 +#: netbox/dcim/models/device_components.py:1543 msgid "inventory item roles" msgstr "" -#: netbox/dcim/models/device_components.py:1587 +#: netbox/dcim/models/device_components.py:1602 #: netbox/dcim/models/devices.py:552 netbox/dcim/models/modules.py:272 #: netbox/dcim/models/racks.py:345 #: netbox/virtualization/models/virtualmachines.py:221 msgid "serial number" msgstr "" -#: netbox/dcim/models/device_components.py:1595 +#: netbox/dcim/models/device_components.py:1610 #: netbox/dcim/models/devices.py:560 netbox/dcim/models/modules.py:279 #: netbox/dcim/models/racks.py:352 msgid "asset tag" msgstr "" -#: netbox/dcim/models/device_components.py:1596 +#: netbox/dcim/models/device_components.py:1611 msgid "A unique tag used to identify this item" msgstr "" -#: netbox/dcim/models/device_components.py:1599 +#: netbox/dcim/models/device_components.py:1614 msgid "discovered" msgstr "" -#: netbox/dcim/models/device_components.py:1601 +#: netbox/dcim/models/device_components.py:1616 msgid "This item was automatically discovered" msgstr "" -#: netbox/dcim/models/device_components.py:1619 +#: netbox/dcim/models/device_components.py:1634 msgid "inventory item" msgstr "" -#: netbox/dcim/models/device_components.py:1620 +#: netbox/dcim/models/device_components.py:1635 msgid "inventory items" msgstr "" -#: netbox/dcim/models/device_components.py:1628 +#: netbox/dcim/models/device_components.py:1643 msgid "Cannot assign self as parent." msgstr "" -#: netbox/dcim/models/device_components.py:1636 +#: netbox/dcim/models/device_components.py:1651 msgid "Parent inventory item does not belong to the same device." msgstr "" -#: netbox/dcim/models/device_components.py:1642 +#: netbox/dcim/models/device_components.py:1657 msgid "Cannot move an inventory item with dependent children" msgstr "" -#: netbox/dcim/models/device_components.py:1650 +#: netbox/dcim/models/device_components.py:1665 msgid "Cannot assign inventory item to component on another device" msgstr "" @@ -7749,8 +7749,8 @@ msgstr "" #: netbox/dcim/tables/devices.py:597 netbox/dcim/views.py:3402 #: netbox/ipam/forms/model_forms.py:805 netbox/ipam/tables/fhrp.py:28 -#: netbox/ipam/ui/panels.py:252 netbox/ipam/views.py:844 -#: netbox/ipam/views.py:968 netbox/netbox/navigation/menu.py:175 +#: netbox/ipam/ui/panels.py:252 netbox/ipam/views.py:916 +#: netbox/ipam/views.py:1041 netbox/netbox/navigation/menu.py:175 #: netbox/netbox/navigation/menu.py:177 netbox/vpn/tables/tunnels.py:98 msgid "IP Addresses" msgstr "" @@ -8184,7 +8184,7 @@ msgstr "" #: netbox/dcim/views.py:3414 netbox/ipam/forms/filtersets.py:469 #: netbox/ipam/models/vlans.py:308 netbox/ipam/tables/ip.py:121 -#: netbox/ipam/tables/vlans.py:52 netbox/ipam/views.py:1302 +#: netbox/ipam/tables/vlans.py:52 netbox/ipam/views.py:1375 #: netbox/netbox/navigation/menu.py:210 netbox/netbox/navigation/menu.py:212 msgid "VLANs" msgstr "" @@ -8284,7 +8284,7 @@ msgid "Changing the type of custom fields is not supported." msgstr "" #: netbox/extras/api/serializers_/scripts.py:50 -#: netbox/extras/forms/scripts.py:91 +#: netbox/extras/forms/scripts.py:91 netbox/extras/forms/scripts.py:100 #, python-brace-format msgid "Error loading script: {error}" msgstr "" @@ -11781,8 +11781,8 @@ msgid "Added" msgstr "" #: netbox/ipam/tables/ip.py:76 netbox/ipam/tables/ip.py:111 -#: netbox/ipam/tables/vlans.py:125 netbox/ipam/views.py:559 -#: netbox/ipam/views.py:1650 netbox/netbox/navigation/menu.py:182 +#: netbox/ipam/tables/vlans.py:125 netbox/ipam/views.py:615 +#: netbox/ipam/views.py:1725 netbox/netbox/navigation/menu.py:182 #: netbox/netbox/navigation/menu.py:184 msgid "Prefixes" msgstr "" @@ -11983,11 +11983,11 @@ msgid "" "are allowed in DNS names" msgstr "" -#: netbox/ipam/views.py:105 netbox/ipam/views.py:1680 +#: netbox/ipam/views.py:105 netbox/ipam/views.py:1755 msgid "Device Interfaces" msgstr "" -#: netbox/ipam/views.py:110 netbox/ipam/views.py:1698 +#: netbox/ipam/views.py:110 netbox/ipam/views.py:1773 msgid "VM Interfaces" msgstr "" @@ -12007,52 +12007,52 @@ msgstr "" msgid "Exporting L2VPNs" msgstr "" -#: netbox/ipam/views.py:728 +#: netbox/ipam/views.py:792 msgid "Duplicate prefixes" msgstr "" -#: netbox/ipam/views.py:729 netbox/ipam/views.py:933 netbox/ipam/views.py:1041 +#: netbox/ipam/views.py:793 netbox/ipam/views.py:1006 netbox/ipam/views.py:1114 msgid "Parent prefixes" msgstr "" -#: netbox/ipam/views.py:781 +#: netbox/ipam/views.py:845 msgid "Child Prefixes" msgstr "" -#: netbox/ipam/views.py:817 +#: netbox/ipam/views.py:889 msgid "Child Ranges" msgstr "" -#: netbox/ipam/views.py:1042 +#: netbox/ipam/views.py:1115 msgid "Duplicate IPs" msgstr "" -#: netbox/ipam/views.py:1046 +#: netbox/ipam/views.py:1119 msgid "Application services" msgstr "" -#: netbox/ipam/views.py:1212 +#: netbox/ipam/views.py:1285 msgid "Related IPs" msgstr "" -#: netbox/ipam/views.py:1351 +#: netbox/ipam/views.py:1426 msgid "VLAN translation rules" msgstr "" -#: netbox/ipam/views.py:1357 +#: netbox/ipam/views.py:1432 msgid "Add Rule" msgstr "" -#: netbox/ipam/views.py:1499 +#: netbox/ipam/views.py:1574 msgid "Virtual IP addresses" msgstr "" -#: netbox/ipam/views.py:1504 netbox/templates/ipam/iprange/ip_addresses.html:7 +#: netbox/ipam/views.py:1579 netbox/templates/ipam/iprange/ip_addresses.html:7 #: netbox/templates/ipam/prefix/ip_addresses.html:7 msgid "Add IP Address" msgstr "" -#: netbox/ipam/views.py:1663 +#: netbox/ipam/views.py:1738 msgid "Add a Prefix" msgstr "" @@ -13251,19 +13251,19 @@ msgstr "" msgid "Copy" msgstr "" -#: netbox/netbox/ui/attrs.py:254 +#: netbox/netbox/ui/attrs.py:271 #, python-brace-format msgid "" "Invalid decoding option: {decoding}! Must be one of {image_decoding_choices}" msgstr "" -#: netbox/netbox/ui/attrs.py:319 +#: netbox/netbox/ui/attrs.py:336 #, python-brace-format msgid "" "Invalid max_items value: {max_items}! Must be a positive integer or None." msgstr "" -#: netbox/netbox/ui/attrs.py:497 +#: netbox/netbox/ui/attrs.py:514 msgid "GPS coordinates" msgstr "" @@ -15909,7 +15909,7 @@ msgid "Expires" msgstr "" #: netbox/users/forms/bulk_edit.py:125 netbox/users/forms/model_forms.py:130 -#: netbox/users/tables.py:47 +#: netbox/users/tables.py:47 netbox/users/ui/panels.py:17 msgid "Allowed IPs" msgstr "" From 583ab535e822ba49cff752fe02e20fbc6a58f4e3 Mon Sep 17 00:00:00 2001 From: mburggraf Date: Wed, 3 Jun 2026 13:08:21 +0200 Subject: [PATCH 16/58] Fixes #22358: Remove broken and unused function get_0u_devices (#22368) --- netbox/dcim/models/racks.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 7a226e0c6..1b7eb1d32 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -638,9 +638,6 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, TrackingModelMixin, RackBase): return elevation.render(face) - def get_0u_devices(self): - return self.devices.filter(position=0) - def get_utilization(self): """ Determine the utilization rate of the rack and return it as a percentage. Occupied and reserved units both count From 62837089b48b3da931004456cfb9a0295534213a Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Wed, 3 Jun 2026 13:14:38 +0200 Subject: [PATCH 17/58] Fixes #21895: Fix missing pagination controls for Job Log entries (#22252) --- netbox/core/tests/test_views.py | 73 +++++++++++++++++++++- netbox/core/views.py | 21 +++++-- netbox/templates/core/job/log_entries.html | 5 ++ 3 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 netbox/templates/core/job/log_entries.html diff --git a/netbox/core/tests/test_views.py b/netbox/core/tests/test_views.py index b763eaed2..b48bc89e7 100644 --- a/netbox/core/tests/test_views.py +++ b/netbox/core/tests/test_views.py @@ -1,7 +1,7 @@ import json import urllib.parse import uuid -from datetime import datetime +from datetime import UTC, datetime from django.contrib.contenttypes.models import ContentType from django.urls import reverse @@ -152,6 +152,77 @@ class JobTestCase( ) +class JobLogViewTestCase(TestCase): + user_permissions = ( + 'core.view_job', + ) + + @classmethod + def setUpTestData(cls): + cls.job = Job.objects.create( + name='Test Job', + job_id=uuid.uuid4(), + ) + cls.job.log_entries = [ + { + 'level': 'info', + 'message': f'log line {i}', + 'timestamp': datetime(2026, 1, 1, tzinfo=UTC), + } + for i in range(120) + ] + cls.job.save() + + def setUp(self): + super().setUp() + # UserConfig.set() mutates self.data in place, which can mutate DEFAULT_USER_PREFERENCES + # (the signal in users/signals.py initializes data with a shared reference). Assign a + # fresh literal instead. Pin per_page so page-boundary assertions don't depend on PAGINATE_COUNT. + self.user.config.data = {'pagination': {'per_page': 50}} + self.user.config.save() + + def test_log_page_renders_table_inline(self): + """The full page renders the first log page inside an HTMX container.""" + url = reverse('core:job_log', kwargs={'pk': self.job.pk}) + response = self.client.get(url) + self.assertHttpStatus(response, 200) + self.assertContains(response, 'htmx-container') + self.assertContains(response, 'log line 0') + self.assertContains(response, 'Showing 1-50 of 120') + + def test_log_page_table_is_embedded(self): + """The embedded table never pushes page/per_page into the browser URL.""" + url = reverse('core:job_log', kwargs={'pk': self.job.pk}) + response = self.client.get(url) + self.assertHttpStatus(response, 200) + self.assertNotContains(response, 'hx-push-url="true"') + + def test_log_table_htmx_renders_partial(self): + """An HTMX request returns the paginated table partial.""" + url = reverse('core:job_log', kwargs={'pk': self.job.pk}) + response = self.client.get(url, headers={'hx-request': 'true'}) + self.assertHttpStatus(response, 200) + self.assertContains(response, 'log line 0') + self.assertContains(response, 'Showing 1-50 of 120') + self.assertContains(response, 'Per Page') + + def test_log_table_htmx_page_navigation(self): + """`?page=2` advances the embedded table to the second page.""" + url = reverse('core:job_log', kwargs={'pk': self.job.pk}) + response = self.client.get(f'{url}?page=2', headers={'hx-request': 'true'}) + self.assertHttpStatus(response, 200) + self.assertContains(response, 'log line 50') + self.assertNotContains(response, 'log line 49') + + def test_log_table_htmx_per_page(self): + """`?per_page=100` widens the embedded table page size.""" + url = reverse('core:job_log', kwargs={'pk': self.job.pk}) + response = self.client.get(f'{url}?per_page=100', headers={'hx-request': 'true'}) + self.assertHttpStatus(response, 200) + self.assertContains(response, 'log line 99') + self.assertNotContains(response, 'log line 100') + + # TODO: Convert to StandardTestCases.Views class ObjectChangeTestCase(TestCase): user_permissions = ( diff --git a/netbox/core/views.py b/netbox/core/views.py index 760346b3d..78059ce2d 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -39,7 +39,6 @@ from netbox.plugins.utils import get_installed_plugins from netbox.ui import layout from netbox.ui.panels import ( CommentsPanel, - ContextTablePanel, JSONPanel, ObjectsTablePanel, PluginContentPanel, @@ -269,7 +268,7 @@ class JobLogView(generic.ObjectView): layout = layout.Layout( layout.Row( layout.Column( - ContextTablePanel('table', title=_('Log Entries')), + TemplatePanel('core/job/log_entries.html', title=_('Log Entries')), PluginContentPanel('left_page'), ), ), @@ -280,13 +279,27 @@ class JobLogView(generic.ObjectView): ), ) - def get_extra_context(self, request, instance): + def get_table(self, request, instance): table = JobLogEntryTable(instance.log_entries) + table.embedded = True + table.htmx_url = reverse('core:job_log', kwargs={'pk': instance.pk}) table.configure(request) + return table + + def get_extra_context(self, request, instance): return { - 'table': table, + 'table': self.get_table(request, instance), } + def get(self, request, **kwargs): + if htmx_partial(request): + instance = self.get_object(**kwargs) + return render(request, 'htmx/table.html', { + 'object': instance, + 'table': self.get_table(request, instance), + }) + return super().get(request, **kwargs) + @register_model_view(Job, 'delete') class JobDeleteView(generic.ObjectDeleteView): diff --git a/netbox/templates/core/job/log_entries.html b/netbox/templates/core/job/log_entries.html new file mode 100644 index 000000000..b675a9f94 --- /dev/null +++ b/netbox/templates/core/job/log_entries.html @@ -0,0 +1,5 @@ +{% extends 'ui/panels/_base.html' %} + +{% block panel_content %} + {% include 'htmx/table.html' %} +{% endblock panel_content %} From d9a58e637699dcb1a25991a0b7cd86ded177cd44 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 3 Jun 2026 07:19:46 -0400 Subject: [PATCH 18/58] Fixes #22357: Remove unused `local_context_data` field from dcim.Module (#22364) --- .../0237_module_remove_local_context_data.py | 15 +++++++++++++++ netbox/dcim/models/modules.py | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 netbox/dcim/migrations/0237_module_remove_local_context_data.py diff --git a/netbox/dcim/migrations/0237_module_remove_local_context_data.py b/netbox/dcim/migrations/0237_module_remove_local_context_data.py new file mode 100644 index 000000000..34464ca27 --- /dev/null +++ b/netbox/dcim/migrations/0237_module_remove_local_context_data.py @@ -0,0 +1,15 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0236_moduletype_component_counts'), + ] + + operations = [ + migrations.RemoveField( + model_name='module', + name='local_context_data', + ), + ] diff --git a/netbox/dcim/models/modules.py b/netbox/dcim/models/modules.py index f52367c6d..5f37b7ee1 100644 --- a/netbox/dcim/models/modules.py +++ b/netbox/dcim/models/modules.py @@ -9,7 +9,7 @@ from mptt.models import MPTTModel from dcim.choices import * from dcim.utils import create_port_mappings, update_interface_bridges -from extras.models import ConfigContextModel, CustomField +from extras.models import CustomField from netbox.models import PrimaryModel from netbox.models.features import ImageAttachmentsMixin from netbox.models.mixins import WeightMixin @@ -240,7 +240,7 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin): return yaml.dump(dict(data), sort_keys=False) -class Module(TrackingModelMixin, PrimaryModel, ConfigContextModel): +class Module(TrackingModelMixin, PrimaryModel): """ 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. From 902aa495ddda4e959b7240f46d1ac097ea5d9785 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Wed, 3 Jun 2026 13:24:10 +0200 Subject: [PATCH 19/58] Closes #18663: Replace assertions with proper error handling (#22344) --- netbox/dcim/forms/bulk_import.py | 3 ++- netbox/dcim/models/cables.py | 5 ++++- netbox/dcim/tests/test_forms.py | 9 +++++++++ netbox/dcim/tests/test_models.py | 12 ++++++++++++ netbox/extras/events.py | 8 +++++++- netbox/extras/tests/test_event_rules.py | 10 ++++++++++ netbox/netbox/models/deletion.py | 14 +++++++++----- netbox/netbox/tests/test_models.py | 10 ++++++++++ netbox/netbox/views/generic/bulk_views.py | 7 +++++-- netbox/utilities/choices.py | 8 +++++--- netbox/utilities/tests/test_choices.py | 14 ++++++++++++++ 11 files changed, 87 insertions(+), 13 deletions(-) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 6f3197919..f756672fe 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -1583,7 +1583,8 @@ class CableImportForm(PrimaryModelImportForm): :param side: 'a' or 'b' """ - assert side in 'ab', f"Invalid side designation: {side}" + if side not in ('a', 'b'): + raise ValueError(_("Invalid side designation: {side}").format(side=side)) device = self.cleaned_data.get(f'side_{side}_device') power_panel = self.cleaned_data.get(f'side_{side}_power_panel') diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index 218300a53..d1c70d942 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -675,7 +675,10 @@ class CableTermination(ChangeLoggedModel): Cache objects related to the termination (e.g. device, rack, site) directly on the object to enable efficient filtering. """ - assert self.termination is not None + if self.termination is None: + raise ValueError( + _("Invalid cable termination: the assigned termination object does not exist.") + ) # Device components if getattr(self.termination, 'device', None): diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py index 3fe568384..2bb0d241d 100644 --- a/netbox/dcim/tests/test_forms.py +++ b/netbox/dcim/tests/test_forms.py @@ -503,6 +503,15 @@ class InterfaceTestCase(TestCase): self.assertNotIn('qinq_svlan', form.cleaned_data.keys()) +class CableTestCase(TestCase): + + def test_invalid_side_designation_raises_value_error(self): + """_clean_side rejects a side other than 'a' or 'b' with ValueError.""" + form = CableImportForm.__new__(CableImportForm) + with self.assertRaisesMessage(ValueError, "Invalid side designation: c"): + form._clean_side('c') + + class SiteFormTestCase(TestCase): """ Tests for M2MAddRemoveFields using Site ASN assignments as the test case. diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 8c2c6a2d2..dc3c2bd24 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -2189,6 +2189,18 @@ class CableTestCase(TestCase): self.assertIsNone(interface.path) +class CableTerminationTestCase(TestCase): + + def test_cache_related_objects_requires_resolvable_termination(self): + """cache_related_objects raises ValueError when the termination cannot be resolved.""" + cable_termination = CableTermination( + termination_type=ObjectType.objects.get_for_model(Interface), + termination_id=0, + ) + with self.assertRaises(ValueError): + cable_termination.cache_related_objects() + + class VirtualDeviceContextTestCase(TestCase): @classmethod diff --git a/netbox/extras/events.py b/netbox/extras/events.py index 470adc875..a2f3aac78 100644 --- a/netbox/extras/events.py +++ b/netbox/extras/events.py @@ -125,7 +125,13 @@ def enqueue_event(queue, instance, request, event_type): app_label = instance._meta.app_label model_name = instance._meta.model_name - assert instance.pk is not None + if instance.pk is None: + raise ValueError( + _("Cannot enqueue an event for an unsaved {app_label}.{model} instance.").format( + app_label=app_label, + model=model_name, + ) + ) key = f'{app_label}.{model_name}:{instance.pk}' if key in queue: diff --git a/netbox/extras/tests/test_event_rules.py b/netbox/extras/tests/test_event_rules.py index 8bf21d175..42faf5bdc 100644 --- a/netbox/extras/tests/test_event_rules.py +++ b/netbox/extras/tests/test_event_rules.py @@ -39,6 +39,16 @@ class EventRuleTestCase(APITestCase): # Clear the queue so leftover jobs do not leak to the next test suite self.queue.empty() + def test_enqueue_event_requires_saved_instance(self): + """enqueue_event raises ValueError for an unsaved instance.""" + request = RequestFactory().get('/') + request.id = uuid.uuid4() + request.user = self.user + site = Site(name='Site 1', slug='site-1') + with patch('extras.events.has_feature', return_value=True): + with self.assertRaises(ValueError): + enqueue_event({}, site, request, OBJECT_CREATED) + @classmethod def setUpTestData(cls): diff --git a/netbox/netbox/models/deletion.py b/netbox/netbox/models/deletion.py index e106fbc10..86ac2bfd7 100644 --- a/netbox/netbox/models/deletion.py +++ b/netbox/netbox/models/deletion.py @@ -3,6 +3,7 @@ import logging from django.contrib.contenttypes.fields import GenericRelation from django.db import router from django.db.models.deletion import CASCADE, Collector +from django.utils.translation import gettext as _ logger = logging.getLogger("netbox.models.deletion") @@ -45,7 +46,7 @@ class CustomCollector(Collector): # Add GenericRelations to the dependency graph processed_relations = set() - for _, instances in list(self.data.items()): + for _model, instances in list(self.data.items()): for instance in instances: # Get all GenericRelations for this model for field in instance._meta.private_fields: @@ -70,10 +71,13 @@ class DeleteMixin: Override delete to use our custom collector. """ using = using or router.db_for_write(self.__class__, instance=self) - assert self._get_pk_val() is not None, ( - f"{self._meta.object_name} object can't be deleted because its " - f"{self._meta.pk.attname} attribute is set to None." - ) + if self._get_pk_val() is None: + raise ValueError( + _("{object_name} object can't be deleted because its {pk_attname} attribute is set to None.").format( + object_name=self._meta.object_name, + pk_attname=self._meta.pk.attname, + ) + ) collector = CustomCollector(using=using) collector.collect([self], keep_parents=keep_parents) diff --git a/netbox/netbox/tests/test_models.py b/netbox/netbox/tests/test_models.py index 0351aea82..d4a931a0d 100644 --- a/netbox/netbox/tests/test_models.py +++ b/netbox/netbox/tests/test_models.py @@ -4,6 +4,7 @@ from django.conf import settings from django.test import TestCase from core.models import ObjectChange +from dcim.models import Site from netbox.tests.dummy_plugin.models import DummyNetBoxModel @@ -21,3 +22,12 @@ class ModelTestCase(TestCase): m.pk = 123 self.assertEqual(m.get_absolute_url(), f'/plugins/dummy-plugin/netboxmodel/{m.pk}/') + + +class DeleteMixinTestCase(TestCase): + + def test_delete_unsaved_instance_raises_value_error(self): + """Deleting an instance with no primary key raises ValueError.""" + site = Site(name='Site 1', slug='site-1') + with self.assertRaises(ValueError): + site.delete() diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index bdd7528d0..65e2a58ae 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -7,7 +7,7 @@ from types import SimpleNamespace from django.conf import settings from django.contrib import messages from django.contrib.contenttypes.fields import GenericForeignKey, GenericRel -from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, ValidationError +from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured, ObjectDoesNotExist, ValidationError from django.db import IntegrityError, router, transaction from django.db.models import ManyToManyField, ProtectedError, RestrictedError from django.db.models.fields.reverse_related import ManyToManyRel @@ -731,7 +731,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): # Update custom fields for name, customfield in custom_fields.items(): - assert name.startswith('cf_') + if not name.startswith('cf_'): + raise ImproperlyConfigured( + _("Custom field form field name must begin with 'cf_': {name}").format(name=name) + ) cf_name = name[3:] # Strip cf_ prefix if name in form.nullable_fields and name in nullified_fields: obj.custom_field_data[cf_name] = None diff --git a/netbox/utilities/choices.py b/netbox/utilities/choices.py index 80151e807..60b24f0df 100644 --- a/netbox/utilities/choices.py +++ b/netbox/utilities/choices.py @@ -1,6 +1,7 @@ import enum from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django.utils.translation import gettext_lazy as _ from utilities.data import get_config_value_ci @@ -20,9 +21,10 @@ class ChoiceSetMeta(type): # Extend static choices with any configured choices if key := attrs.get('key'): - assert type(attrs['CHOICES']) is list, _( - "{name} has a key defined but CHOICES is not a list" - ).format(name=name) + if type(attrs['CHOICES']) is not list: + raise ImproperlyConfigured( + _("{name} has a key defined but CHOICES is not a list").format(name=name) + ) app = attrs['__module__'].split('.', 1)[0] replace_key = f'{app}.{key}' replace_choices = get_config_value_ci(settings.FIELD_CHOICES, replace_key) diff --git a/netbox/utilities/tests/test_choices.py b/netbox/utilities/tests/test_choices.py index 2bfd8fd5d..4005dfcec 100644 --- a/netbox/utilities/tests/test_choices.py +++ b/netbox/utilities/tests/test_choices.py @@ -1,3 +1,4 @@ +from django.core.exceptions import ImproperlyConfigured from django.test import TestCase, override_settings from utilities.choices import ChoiceSet @@ -31,6 +32,19 @@ class ChoiceSetTestCase(TestCase): def test_values(self): self.assertListEqual(ExampleChoices.values(), ['a', 'b', 'c', 1, 2, 3]) + def test_key_with_non_list_choices_raises(self): + """A ChoiceSet declaring a key must define CHOICES as a list.""" + with self.assertRaises(ImproperlyConfigured): + type( + 'InvalidChoices', + (ChoiceSet,), + { + '__module__': __name__, + 'key': 'invalid_choices', + 'CHOICES': (('foo', 'Foo'),), + }, + ) + class FieldChoicesCaseInsensitiveTestCase(TestCase): """ From 2e50fc3d97e81f6bac394547415853c00a7950f7 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Wed, 3 Jun 2026 20:06:18 +0200 Subject: [PATCH 20/58] fix(extras): Add choice_value lookup for ChoiceSetField (#22366) Introduce ChoiceSetField as ArrayField subclass for custom field choices and implement choice_value lookup to filter by value element only. Update GraphQL filter to use ExtraChoicesLookup with contains and length options. Fixes #22324 --- netbox/extras/fields.py | 23 +++++++- netbox/extras/graphql/filter_lookups.py | 30 ++++++++++ netbox/extras/graphql/filters.py | 3 +- netbox/extras/lookups.py | 28 ++++++++- ...lter_customfieldchoiceset_extra_choices.py | 18 ++++++ netbox/extras/models/customfields.py | 8 +-- netbox/extras/tests/test_api.py | 58 +++++++++++++++++++ netbox/extras/tests/test_lookups.py | 31 ++++++++++ netbox/utilities/serializers/json.py | 11 +++- netbox/utilities/testing/base.py | 4 +- netbox/utilities/tests/test_serialization.py | 13 ++++- 11 files changed, 212 insertions(+), 15 deletions(-) create mode 100644 netbox/extras/graphql/filter_lookups.py create mode 100644 netbox/extras/migrations/0139_alter_customfieldchoiceset_extra_choices.py create mode 100644 netbox/extras/tests/test_lookups.py diff --git a/netbox/extras/fields.py b/netbox/extras/fields.py index 6cd44432f..735471757 100644 --- a/netbox/extras/fields.py +++ b/netbox/extras/fields.py @@ -1,4 +1,10 @@ -from django.db.models import TextField +from django.contrib.postgres.fields import ArrayField +from django.db.models import CharField, TextField + +__all__ = ( + 'CachedValueField', + 'ChoiceSetField', +) class CachedValueField(TextField): @@ -6,3 +12,18 @@ class CachedValueField(TextField): Currently a dummy field to prevent custom lookups being applied globally to TextField. """ pass + + +class ChoiceSetField(ArrayField): + """ + An ArrayField of two-element [value, label] string pairs representing custom field choices. + """ + def __init__(self, **kwargs): + kwargs['base_field'] = ArrayField(base_field=CharField(max_length=100), size=2) + super().__init__(**kwargs) + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + # base_field is fixed by __init__ and omitted from migrations + del kwargs['base_field'] + return name, path, args, kwargs diff --git a/netbox/extras/graphql/filter_lookups.py b/netbox/extras/graphql/filter_lookups.py new file mode 100644 index 000000000..8394f8541 --- /dev/null +++ b/netbox/extras/graphql/filter_lookups.py @@ -0,0 +1,30 @@ +import strawberry +import strawberry_django +from django.db.models import Q, QuerySet +from strawberry.directive import DirectiveValue +from strawberry.types import Info + +__all__ = ( + 'ExtraChoicesLookup', +) + + +@strawberry.input( + one_of=True, + description='Lookup for extra choices defined on a choice set. Only one of the lookup fields can be set.', +) +class ExtraChoicesLookup: + contains: str | None = strawberry.field( + default=strawberry.UNSET, description='Has an extra choice with this value' + ) + length: int | None = strawberry.field( + default=strawberry.UNSET, description='Number of extra choices' + ) + + @strawberry_django.filter_field + def filter(self, info: Info, queryset: QuerySet, prefix: DirectiveValue[str] = '') -> tuple[QuerySet, Q]: + if self.contains is not strawberry.UNSET and self.contains is not None: + return queryset, Q(**{f'{prefix}choice_value': self.contains}) + if self.length is not strawberry.UNSET and self.length is not None: + return queryset, Q(**{f'{prefix}len': self.length}) + return queryset, Q() diff --git a/netbox/extras/graphql/filters.py b/netbox/extras/graphql/filters.py index afc1ff8f9..2479d46d2 100644 --- a/netbox/extras/graphql/filters.py +++ b/netbox/extras/graphql/filters.py @@ -23,6 +23,7 @@ if TYPE_CHECKING: SiteFilter, SiteGroupFilter, ) + from extras.graphql.filter_lookups import ExtraChoicesLookup from netbox.graphql.enums import ColorEnum from netbox.graphql.filter_lookups import FloatLookup, IntegerLookup, JSONFilter, StringArrayLookup, TreeNodeFilter from tenancy.graphql.filters import TenantFilter, TenantGroupFilter @@ -198,7 +199,7 @@ class CustomFieldChoiceSetFilter(ChangeLoggedModelFilter): ) = ( strawberry_django.filter_field() ) - extra_choices: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + extra_choices: Annotated['ExtraChoicesLookup', strawberry.lazy('extras.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) order_alphabetically: FilterLookup[bool] | None = strawberry_django.filter_field() diff --git a/netbox/extras/lookups.py b/netbox/extras/lookups.py index 062a2a01b..e1b4d2339 100644 --- a/netbox/extras/lookups.py +++ b/netbox/extras/lookups.py @@ -3,7 +3,16 @@ from django.contrib.postgres.fields.ranges import RangeField from django.db.models import CharField, JSONField, Lookup from django.db.models.fields.json import KeyTextTransform -from .fields import CachedValueField +from .fields import CachedValueField, ChoiceSetField + +__all__ = ( + 'ChoiceValueLookup', + 'Empty', + 'JSONEmpty', + 'NetContainsOrEquals', + 'NetHost', + 'RangeContains', +) class RangeContains(Lookup): @@ -34,6 +43,22 @@ class RangeContains(Lookup): return sql, params +class ChoiceValueLookup(Lookup): + """ + Match rows where any [value, label] pair in a ChoiceSetField has the given value. + + Compares the RHS against the first element (the value) of each pair. + """ + lookup_name = 'choice_value' + prepare_rhs = False + + def as_sql(self, compiler, connection): + lhs, lhs_params = self.process_lhs(compiler, connection) + rhs, rhs_params = self.process_rhs(compiler, connection) + # Slice the value column of the two-dimensional array and match any element + return f'{rhs} = ANY({lhs}[:][1:1])', [*rhs_params, *lhs_params] + + class Empty(Lookup): """ Filter on whether a string is empty. @@ -99,6 +124,7 @@ class NetContainsOrEquals(Lookup): ArrayField.register_lookup(RangeContains) +ChoiceSetField.register_lookup(ChoiceValueLookup) CharField.register_lookup(Empty) JSONField.register_lookup(JSONEmpty) CachedValueField.register_lookup(NetHost) diff --git a/netbox/extras/migrations/0139_alter_customfieldchoiceset_extra_choices.py b/netbox/extras/migrations/0139_alter_customfieldchoiceset_extra_choices.py new file mode 100644 index 000000000..b0f5704c6 --- /dev/null +++ b/netbox/extras/migrations/0139_alter_customfieldchoiceset_extra_choices.py @@ -0,0 +1,18 @@ +from django.db import migrations + +import extras.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0138_customfieldchoiceset_choice_colors'), + ] + + operations = [ + migrations.AlterField( + model_name='customfieldchoiceset', + name='extra_choices', + field=extras.fields.ChoiceSetField(blank=True, null=True), + ), + ] diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 011afa42d..a076e2538 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -7,7 +7,6 @@ import django_filters import jsonschema from django import forms from django.conf import settings -from django.contrib.postgres.fields import ArrayField from django.core.validators import RegexValidator, ValidationError from django.db import models from django.db.models import F, Func, Value @@ -21,6 +20,7 @@ from jsonschema.exceptions import ValidationError as JSONValidationError from core.models import ObjectType from extras.choices import * from extras.data import CHOICE_SETS +from extras.fields import ChoiceSetField from netbox.context import query_cache from netbox.models import ChangeLoggedModel from netbox.models.features import CloningMixin, ExportTemplatesMixin @@ -877,11 +877,7 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, OwnerMixin, Chang null=True, help_text=_('Base set of predefined choices (optional)') ) - extra_choices = ArrayField( - ArrayField( - base_field=models.CharField(max_length=100), - size=2 - ), + extra_choices = ChoiceSetField( blank=True, null=True ) diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index df94cbe0d..04ff9d424 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -325,6 +325,64 @@ class CustomFieldChoiceSetTestCase(APIViewTestCases.APIViewTestCase): response = self.client.post(self._get_list_url(), data, format='json', **self.header) self.assertEqual(response.status_code, 400) + def test_graphql_filter_extra_choices(self): + """Filter choice sets by choice value and by number of choices.""" + self.add_permissions('extras.view_customfieldchoiceset') + + # '1A' appears here only as a label, so it must not match contains + CustomFieldChoiceSet.objects.create( + name='Choice Set Labels', + extra_choices=[['sel1', 'Selection 1'], ['other', '1A']], + ) + + def run(lookup): + query = '{ custom_field_choice_set_list(filters: {extra_choices: ' + lookup + '}) { name } }' + response = self.client.post(reverse('graphql'), data={'query': query}, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + data = response.json() + self.assertNotIn('errors', data) + return sorted(row['name'] for row in data['data']['custom_field_choice_set_list']) + + # contains matches choice values only, never labels + self.assertEqual(run('{contains: "1A"}'), ['Choice Set 1']) + self.assertEqual(run('{contains: "sel1"}'), ['Choice Set Labels']) + self.assertEqual(run('{contains: "Selection 1"}'), []) + # length is the number of [value, label] pairs + self.assertEqual(run('{length: 2}'), ['Choice Set Labels']) + self.assertEqual(run('{length: 1}'), []) + + def test_graphql_filter_extra_choices_rejects_array_operands(self): + """The legacy flat and nested array operand shapes fail schema validation.""" + self.add_permissions('extras.view_customfieldchoiceset') + + def run_invalid(lookup): + query = '{ custom_field_choice_set_list(filters: {extra_choices: ' + lookup + '}) { name } }' + response = self.client.post(reverse('graphql'), data={'query': query}, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertIn('errors', response.json()) + + # shapes advertised or attempted before #22324 + run_invalid('{contains: ["1A"]}') + run_invalid('{contains: [["1A", "Choice 1A"]]}') + + def test_graphql_filter_extra_choices_via_relation(self): + """The extra_choices lookup composes through the choice_set relation prefix.""" + self.add_permissions('extras.view_customfield') + + for choice_set in CustomFieldChoiceSet.objects.filter(name__in=['Choice Set 1', 'Choice Set 2']): + CustomField.objects.create( + name=f'cf_{choice_set.name[-1]}', + type=CustomFieldTypeChoices.TYPE_SELECT, + choice_set=choice_set, + ) + + query = '{ custom_field_list(filters: {choice_set: {extra_choices: {contains: "1A"}}}) { name } }' + response = self.client.post(reverse('graphql'), data={'query': query}, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + data = response.json() + self.assertNotIn('errors', data) + self.assertEqual([row['name'] for row in data['data']['custom_field_list']], ['cf_1']) + class CustomLinkTestCase(APIViewTestCases.APIViewTestCase): model = CustomLink diff --git a/netbox/extras/tests/test_lookups.py b/netbox/extras/tests/test_lookups.py new file mode 100644 index 000000000..7188c2ed6 --- /dev/null +++ b/netbox/extras/tests/test_lookups.py @@ -0,0 +1,31 @@ +from django.core.exceptions import FieldError +from django.test import TestCase + +from extras.choices import CustomFieldChoiceSetBaseChoices +from extras.models import CustomFieldChoiceSet, EventRule + + +class ChoiceValueLookupTestCase(TestCase): + + def test_choice_value_matches_values_only(self): + """choice_value matches the value element of a pair, never the label.""" + CustomFieldChoiceSet.objects.create( + name='Choice Set 1', + extra_choices=[['sel1', 'Selection 1'], ['other', 'sel2']], + ) + self.assertEqual(CustomFieldChoiceSet.objects.filter(extra_choices__choice_value='sel1').count(), 1) + self.assertEqual(CustomFieldChoiceSet.objects.filter(extra_choices__choice_value='sel2').count(), 0) + + def test_choice_value_excludes_null_extra_choices(self): + """Choice sets without extra choices are excluded without raising.""" + CustomFieldChoiceSet.objects.create( + name='Base Only', + base_choices=CustomFieldChoiceSetBaseChoices.IATA, + ) + self.assertEqual(CustomFieldChoiceSet.objects.filter(extra_choices__choice_value='sel1').count(), 0) + self.assertEqual(CustomFieldChoiceSet.objects.filter(extra_choices__len=2).count(), 0) + + def test_choice_value_not_registered_on_plain_array_fields(self): + """choice_value is scoped to ChoiceSetField and unavailable on other ArrayFields.""" + with self.assertRaises(FieldError): + EventRule.objects.filter(event_types__choice_value='x').exists() diff --git a/netbox/utilities/serializers/json.py b/netbox/utilities/serializers/json.py index ee7dd6cb2..b1e35e41e 100644 --- a/netbox/utilities/serializers/json.py +++ b/netbox/utilities/serializers/json.py @@ -1,10 +1,15 @@ from django.contrib.postgres.fields import ArrayField -from django.core.serializers.json import Deserializer # noqa: F401 +from django.core.serializers.json import Deserializer from django.core.serializers.json import Serializer as Serializer_ from django.utils.encoding import is_protected_type # NOTE: Module must contain both Serializer and Deserializer +__all__ = ( + 'Deserializer', + 'Serializer', +) + class Serializer(Serializer_): """ @@ -14,8 +19,8 @@ class Serializer(Serializer_): def _value_from_field(self, obj, field): value = field.value_from_object(obj) - # Handle ArrayFields of protected types - if type(field) is ArrayField: + # Handle ArrayFields (including subclasses) of protected types + if isinstance(field, ArrayField): if not value or is_protected_type(value[0]): return value diff --git a/netbox/utilities/testing/base.py b/netbox/utilities/testing/base.py index 12e815f2e..d697d4eb6 100644 --- a/netbox/utilities/testing/base.py +++ b/netbox/utilities/testing/base.py @@ -198,8 +198,8 @@ class ModelTestCase(TestCase): model_dict[key] = [[r.lower, r.upper - 1] for r in value] else: - # Convert ArrayFields to CSV strings - if type(field) is ArrayField: + # Convert ArrayFields (including subclasses) to CSV strings + if isinstance(field, ArrayField): if getattr(field.base_field, 'choices', None): # Values for fields with pre-defined choices can be returned as lists model_dict[key] = value diff --git a/netbox/utilities/tests/test_serialization.py b/netbox/utilities/tests/test_serialization.py index 044b52cc1..b883dbd06 100644 --- a/netbox/utilities/tests/test_serialization.py +++ b/netbox/utilities/tests/test_serialization.py @@ -2,7 +2,8 @@ from django.test import TestCase from dcim.choices import SiteStatusChoices from dcim.models import Site -from extras.models import Tag +from extras.choices import CustomFieldChoiceSetBaseChoices +from extras.models import CustomFieldChoiceSet, Tag from utilities.serialization import deserialize_object, serialize_object @@ -32,6 +33,16 @@ class SerializationTestCase(TestCase): self.assertEqual(data['foo'], 123) self.assertNotIn('description', data) + def test_serialize_object_empty_array_field_subclass(self): + """An empty ArrayField subclass value serializes as a list, not a string.""" + choice_set = CustomFieldChoiceSet.objects.create( + name='Choice Set 1', + base_choices=CustomFieldChoiceSetBaseChoices.IATA, + extra_choices=[], + ) + data = serialize_object(choice_set) + self.assertEqual(data['extra_choices'], []) + def test_deserialize_object(self): data = { 'name': 'Site 1', From d4d931dd4f9292c2524f3b2b7138d31225b3e936 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 06:31:28 +0000 Subject: [PATCH 21/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 215 +++++++++++-------- 1 file changed, 121 insertions(+), 94 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 221179f88..767f0f0ea 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-03 06:34+0000\n" +"POT-Creation-Date: 2026-06-04 06:31+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -173,8 +173,8 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:339 netbox/dcim/forms/bulk_edit.py:696 #: netbox/dcim/forms/bulk_edit.py:906 netbox/dcim/forms/bulk_import.py:148 #: netbox/dcim/forms/bulk_import.py:256 netbox/dcim/forms/bulk_import.py:365 -#: netbox/dcim/forms/bulk_import.py:656 netbox/dcim/forms/bulk_import.py:1700 -#: netbox/dcim/forms/bulk_import.py:1728 netbox/dcim/forms/filtersets.py:108 +#: netbox/dcim/forms/bulk_import.py:656 netbox/dcim/forms/bulk_import.py:1701 +#: netbox/dcim/forms/bulk_import.py:1729 netbox/dcim/forms/filtersets.py:108 #: netbox/dcim/forms/filtersets.py:258 netbox/dcim/forms/filtersets.py:390 #: netbox/dcim/forms/filtersets.py:500 netbox/dcim/forms/filtersets.py:895 #: netbox/dcim/forms/filtersets.py:1117 netbox/dcim/forms/filtersets.py:1198 @@ -481,7 +481,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:881 netbox/dcim/forms/bulk_import.py:902 #: netbox/dcim/forms/bulk_import.py:988 netbox/dcim/forms/bulk_import.py:1117 #: netbox/dcim/forms/bulk_import.py:1136 netbox/dcim/forms/bulk_import.py:1515 -#: netbox/dcim/forms/bulk_import.py:1765 netbox/dcim/forms/filtersets.py:1155 +#: netbox/dcim/forms/bulk_import.py:1766 netbox/dcim/forms/filtersets.py:1155 #: netbox/dcim/forms/filtersets.py:1268 netbox/dcim/forms/filtersets.py:1401 #: netbox/dcim/forms/filtersets.py:1492 netbox/dcim/forms/filtersets.py:1512 #: netbox/dcim/forms/filtersets.py:1532 netbox/dcim/forms/filtersets.py:1552 @@ -542,7 +542,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:281 netbox/dcim/forms/bulk_import.py:390 #: netbox/dcim/forms/bulk_import.py:621 netbox/dcim/forms/bulk_import.py:781 #: netbox/dcim/forms/bulk_import.py:1258 netbox/dcim/forms/bulk_import.py:1503 -#: netbox/dcim/forms/bulk_import.py:1760 netbox/dcim/forms/bulk_import.py:1823 +#: netbox/dcim/forms/bulk_import.py:1761 netbox/dcim/forms/bulk_import.py:1824 #: netbox/dcim/forms/filtersets.py:210 netbox/dcim/forms/filtersets.py:270 #: netbox/dcim/forms/filtersets.py:413 netbox/dcim/forms/filtersets.py:528 #: netbox/dcim/forms/filtersets.py:941 netbox/dcim/forms/filtersets.py:1064 @@ -604,7 +604,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:124 netbox/dcim/forms/bulk_import.py:169 #: netbox/dcim/forms/bulk_import.py:267 netbox/dcim/forms/bulk_import.py:395 #: netbox/dcim/forms/bulk_import.py:595 netbox/dcim/forms/bulk_import.py:1521 -#: netbox/dcim/forms/bulk_import.py:1816 netbox/dcim/forms/filtersets.py:145 +#: netbox/dcim/forms/bulk_import.py:1817 netbox/dcim/forms/filtersets.py:145 #: netbox/dcim/forms/filtersets.py:204 netbox/dcim/forms/filtersets.py:237 #: netbox/dcim/forms/filtersets.py:374 netbox/dcim/forms/filtersets.py:459 #: netbox/dcim/forms/filtersets.py:480 netbox/dcim/forms/filtersets.py:863 @@ -921,7 +921,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:107 netbox/dcim/forms/bulk_import.py:166 #: netbox/dcim/forms/bulk_import.py:283 netbox/dcim/forms/bulk_import.py:392 #: netbox/dcim/forms/bulk_import.py:623 netbox/dcim/forms/bulk_import.py:783 -#: netbox/dcim/forms/bulk_import.py:1260 netbox/dcim/forms/bulk_import.py:1762 +#: netbox/dcim/forms/bulk_import.py:1260 netbox/dcim/forms/bulk_import.py:1763 #: netbox/ipam/forms/bulk_import.py:207 netbox/ipam/forms/bulk_import.py:271 #: netbox/ipam/forms/bulk_import.py:307 netbox/ipam/forms/bulk_import.py:538 #: netbox/ipam/forms/bulk_import.py:551 @@ -937,8 +937,8 @@ msgstr "" #: netbox/circuits/forms/bulk_import.py:235 #: netbox/dcim/forms/bulk_import.py:128 netbox/dcim/forms/bulk_import.py:173 #: netbox/dcim/forms/bulk_import.py:399 netbox/dcim/forms/bulk_import.py:599 -#: netbox/dcim/forms/bulk_import.py:1525 netbox/dcim/forms/bulk_import.py:1757 -#: netbox/dcim/forms/bulk_import.py:1820 netbox/ipam/forms/bulk_import.py:49 +#: netbox/dcim/forms/bulk_import.py:1525 netbox/dcim/forms/bulk_import.py:1758 +#: netbox/dcim/forms/bulk_import.py:1821 netbox/ipam/forms/bulk_import.py:49 #: netbox/ipam/forms/bulk_import.py:78 netbox/ipam/forms/bulk_import.py:106 #: netbox/ipam/forms/bulk_import.py:126 netbox/ipam/forms/bulk_import.py:153 #: netbox/ipam/forms/bulk_import.py:181 netbox/ipam/forms/bulk_import.py:266 @@ -1012,8 +1012,8 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:462 netbox/dcim/forms/bulk_edit.py:701 #: netbox/dcim/forms/bulk_edit.py:750 netbox/dcim/forms/bulk_edit.py:915 #: netbox/dcim/forms/bulk_import.py:261 netbox/dcim/forms/bulk_import.py:371 -#: netbox/dcim/forms/bulk_import.py:662 netbox/dcim/forms/bulk_import.py:1706 -#: netbox/dcim/forms/bulk_import.py:1740 netbox/dcim/forms/filtersets.py:116 +#: netbox/dcim/forms/bulk_import.py:662 netbox/dcim/forms/bulk_import.py:1707 +#: netbox/dcim/forms/bulk_import.py:1741 netbox/dcim/forms/filtersets.py:116 #: netbox/dcim/forms/filtersets.py:369 netbox/dcim/forms/filtersets.py:404 #: netbox/dcim/forms/filtersets.py:455 netbox/dcim/forms/filtersets.py:508 #: netbox/dcim/forms/filtersets.py:860 netbox/dcim/forms/filtersets.py:904 @@ -1301,7 +1301,7 @@ msgstr "" #: netbox/dcim/models/device_components.py:1575 #: netbox/dcim/models/devices.py:599 netbox/dcim/models/devices.py:1261 #: netbox/dcim/models/modules.py:264 netbox/dcim/models/power.py:95 -#: netbox/dcim/models/racks.py:329 netbox/dcim/models/racks.py:716 +#: netbox/dcim/models/racks.py:329 netbox/dcim/models/racks.py:713 #: netbox/dcim/models/sites.py:163 netbox/dcim/models/sites.py:287 #: netbox/ipam/models/ip.py:252 netbox/ipam/models/ip.py:554 #: netbox/ipam/models/ip.py:792 netbox/ipam/models/vlans.py:242 @@ -1400,7 +1400,7 @@ msgstr "" #: netbox/circuits/models/circuits.py:301 #: netbox/circuits/models/virtual_circuits.py:149 #: netbox/dcim/models/device_component_templates.py:69 -#: netbox/dcim/models/device_components.py:67 netbox/dcim/models/racks.py:733 +#: netbox/dcim/models/device_components.py:67 netbox/dcim/models/racks.py:730 #: netbox/extras/models/configs.py:42 netbox/extras/models/configs.py:95 #: netbox/extras/models/configs.py:286 netbox/extras/models/customfields.py:152 #: netbox/extras/models/models.py:72 netbox/extras/models/models.py:181 @@ -1690,7 +1690,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:1112 netbox/dcim/forms/bulk_import.py:1131 #: netbox/dcim/forms/bulk_import.py:1150 netbox/dcim/forms/bulk_import.py:1168 #: netbox/dcim/forms/bulk_import.py:1222 netbox/dcim/forms/bulk_import.py:1344 -#: netbox/dcim/forms/bulk_import.py:1810 netbox/dcim/forms/connections.py:34 +#: netbox/dcim/forms/bulk_import.py:1811 netbox/dcim/forms/connections.py:34 #: netbox/dcim/forms/filtersets.py:158 netbox/dcim/forms/filtersets.py:1061 #: netbox/dcim/forms/filtersets.py:1098 netbox/dcim/forms/filtersets.py:1265 #: netbox/dcim/forms/filtersets.py:1486 netbox/dcim/forms/filtersets.py:1509 @@ -2459,7 +2459,7 @@ msgstr "" msgid "last updated" msgstr "" -#: netbox/core/models/data.py:304 netbox/dcim/models/cables.py:741 +#: netbox/core/models/data.py:304 netbox/dcim/models/cables.py:744 msgid "path" msgstr "" @@ -2676,7 +2676,7 @@ msgstr "" msgid "Interval" msgstr "" -#: netbox/core/tables/jobs.py:51 netbox/core/views.py:272 +#: netbox/core/tables/jobs.py:51 netbox/core/views.py:271 msgid "Log Entries" msgstr "" @@ -2842,7 +2842,7 @@ msgstr "" msgid "Change" msgstr "" -#: netbox/core/utils.py:87 netbox/core/utils.py:153 netbox/core/views.py:557 +#: netbox/core/utils.py:87 netbox/core/utils.py:153 netbox/core/views.py:570 #, python-brace-format msgid "Job {job_id} not found" msgstr "" @@ -2852,62 +2852,62 @@ msgstr "" msgid "Job {id} not found." msgstr "" -#: netbox/core/views.py:136 +#: netbox/core/views.py:135 #, python-brace-format msgid "Queued job #{id} to sync {datasource}" msgstr "" -#: netbox/core/views.py:254 netbox/extras/forms/filtersets.py:184 +#: netbox/core/views.py:253 netbox/extras/forms/filtersets.py:184 #: netbox/extras/forms/filtersets.py:385 netbox/extras/forms/filtersets.py:408 #: netbox/extras/forms/filtersets.py:504 netbox/extras/forms/model_forms.py:765 #: netbox/extras/ui/panels.py:381 msgid "Data" msgstr "" -#: netbox/core/views.py:265 netbox/templates/extras/htmx/script_result.html:43 +#: netbox/core/views.py:264 netbox/templates/extras/htmx/script_result.html:43 msgid "Log" msgstr "" -#: netbox/core/views.py:493 +#: netbox/core/views.py:506 #, python-brace-format msgid "Restored configuration revision #{id}" msgstr "" -#: netbox/core/views.py:596 +#: netbox/core/views.py:609 #, python-brace-format msgid "Job {id} has been deleted." msgstr "" -#: netbox/core/views.py:598 +#: netbox/core/views.py:611 #, python-brace-format msgid "Error deleting job {id}: {error}" msgstr "" -#: netbox/core/views.py:607 +#: netbox/core/views.py:620 #, python-brace-format msgid "Job {id} has been re-enqueued." msgstr "" -#: netbox/core/views.py:616 +#: netbox/core/views.py:629 #, python-brace-format msgid "Job {id} has been enqueued." msgstr "" -#: netbox/core/views.py:625 +#: netbox/core/views.py:638 #, python-brace-format msgid "Job {id} has been stopped." msgstr "" -#: netbox/core/views.py:627 +#: netbox/core/views.py:640 #, python-brace-format msgid "Failed to stop job {id}" msgstr "" -#: netbox/core/views.py:841 +#: netbox/core/views.py:854 msgid "Plugins catalog could not be loaded" msgstr "" -#: netbox/core/views.py:877 +#: netbox/core/views.py:890 #, python-brace-format msgid "Plugin {name} not found" msgstr "" @@ -4399,8 +4399,8 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:460 netbox/dcim/forms/bulk_edit.py:937 #: netbox/dcim/forms/bulk_import.py:378 netbox/dcim/forms/bulk_import.py:381 -#: netbox/dcim/forms/bulk_import.py:669 netbox/dcim/forms/bulk_import.py:1747 -#: netbox/dcim/forms/bulk_import.py:1751 netbox/dcim/forms/filtersets.py:125 +#: netbox/dcim/forms/bulk_import.py:669 netbox/dcim/forms/bulk_import.py:1748 +#: netbox/dcim/forms/bulk_import.py:1752 netbox/dcim/forms/filtersets.py:125 #: netbox/dcim/forms/filtersets.py:370 netbox/dcim/forms/filtersets.py:465 #: netbox/dcim/forms/filtersets.py:479 netbox/dcim/forms/filtersets.py:525 #: netbox/dcim/forms/filtersets.py:914 netbox/dcim/forms/filtersets.py:1130 @@ -4603,17 +4603,17 @@ msgstr "" msgid "Domain" msgstr "" -#: netbox/dcim/forms/bulk_edit.py:932 netbox/dcim/forms/bulk_import.py:1734 +#: netbox/dcim/forms/bulk_edit.py:932 netbox/dcim/forms/bulk_import.py:1735 #: netbox/dcim/forms/filtersets.py:1384 netbox/dcim/forms/model_forms.py:927 msgid "Power panel" msgstr "" -#: netbox/dcim/forms/bulk_edit.py:954 netbox/dcim/forms/bulk_import.py:1770 +#: netbox/dcim/forms/bulk_edit.py:954 netbox/dcim/forms/bulk_import.py:1771 #: netbox/dcim/forms/filtersets.py:1406 msgid "Supply" msgstr "" -#: netbox/dcim/forms/bulk_edit.py:960 netbox/dcim/forms/bulk_import.py:1775 +#: netbox/dcim/forms/bulk_edit.py:960 netbox/dcim/forms/bulk_import.py:1776 #: netbox/dcim/forms/filtersets.py:1411 msgid "Phase" msgstr "" @@ -4834,7 +4834,7 @@ msgid "available options" msgstr "" #: netbox/dcim/forms/bulk_import.py:151 netbox/dcim/forms/bulk_import.py:659 -#: netbox/dcim/forms/bulk_import.py:1731 netbox/ipam/forms/bulk_import.py:519 +#: netbox/dcim/forms/bulk_import.py:1732 netbox/ipam/forms/bulk_import.py:519 #: netbox/virtualization/forms/bulk_import.py:66 msgid "Assigned site" msgstr "" @@ -4906,7 +4906,7 @@ msgstr "" msgid "Parent site" msgstr "" -#: netbox/dcim/forms/bulk_import.py:375 netbox/dcim/forms/bulk_import.py:1744 +#: netbox/dcim/forms/bulk_import.py:375 netbox/dcim/forms/bulk_import.py:1745 msgid "Rack's location (if any)" msgstr "" @@ -4971,7 +4971,7 @@ msgstr "" msgid "Limit platform assignments to this manufacturer" msgstr "" -#: netbox/dcim/forms/bulk_import.py:592 netbox/dcim/forms/bulk_import.py:1813 +#: netbox/dcim/forms/bulk_import.py:592 netbox/dcim/forms/bulk_import.py:1814 #: netbox/tenancy/forms/bulk_import.py:116 msgid "Assigned role" msgstr "" @@ -5326,81 +5326,86 @@ msgstr "" msgid "Color name (e.g. \"Red\") or hex code (e.g. \"f44336\")" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1605 +#: netbox/dcim/forms/bulk_import.py:1587 +#, python-brace-format +msgid "Invalid side designation: {side}" +msgstr "" + +#: netbox/dcim/forms/bulk_import.py:1606 #, python-brace-format msgid "" "Side {side_upper}: {power_panel} {termination_object} is already connected" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1611 +#: netbox/dcim/forms/bulk_import.py:1612 #, python-brace-format msgid "{side_upper} side termination not found: {power_panel} {name}" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1629 +#: netbox/dcim/forms/bulk_import.py:1630 #, python-brace-format msgid "Side {side_upper}: {device} {termination_object} is already connected" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1635 +#: netbox/dcim/forms/bulk_import.py:1636 #, python-brace-format msgid "{side_upper} side termination not found: {device} {name}" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1657 +#: netbox/dcim/forms/bulk_import.py:1658 #, python-brace-format msgid "" "{color} did not match any used color name and was longer than six " "characters: invalid hex." msgstr "" -#: netbox/dcim/forms/bulk_import.py:1682 netbox/dcim/forms/model_forms.py:962 +#: netbox/dcim/forms/bulk_import.py:1683 netbox/dcim/forms/model_forms.py:962 #: netbox/dcim/tables/devices.py:1154 #: netbox/templates/dcim/panels/virtual_chassis_members.html:10 msgid "Master" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1686 +#: netbox/dcim/forms/bulk_import.py:1687 msgid "Master device" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1703 +#: netbox/dcim/forms/bulk_import.py:1704 msgid "Name of parent site" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1737 +#: netbox/dcim/forms/bulk_import.py:1738 msgid "Upstream power panel" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1767 +#: netbox/dcim/forms/bulk_import.py:1768 msgid "Primary or redundant" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1772 +#: netbox/dcim/forms/bulk_import.py:1773 msgid "Supply type (AC/DC)" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1777 +#: netbox/dcim/forms/bulk_import.py:1778 msgid "Single or three-phase" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1827 netbox/dcim/forms/model_forms.py:1939 +#: netbox/dcim/forms/bulk_import.py:1828 netbox/dcim/forms/model_forms.py:1939 #: netbox/dcim/ui/panels.py:111 netbox/dcim/ui/panels.py:371 #: netbox/virtualization/ui/panels.py:51 msgid "Primary IPv4" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1831 +#: netbox/dcim/forms/bulk_import.py:1832 msgid "IPv4 address with mask, e.g. 1.2.3.4/24" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1834 netbox/dcim/forms/model_forms.py:1948 +#: netbox/dcim/forms/bulk_import.py:1835 netbox/dcim/forms/model_forms.py:1948 #: netbox/dcim/ui/panels.py:116 netbox/dcim/ui/panels.py:376 #: netbox/virtualization/ui/panels.py:56 msgid "Primary IPv6" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1838 +#: netbox/dcim/forms/bulk_import.py:1839 msgid "IPv6 address with prefix length, e.g. 2001:db8::1/64" msgstr "" @@ -6039,43 +6044,48 @@ msgstr "" msgid "Circuit terminations attached to a provider network may not be cabled." msgstr "" -#: netbox/dcim/models/cables.py:745 netbox/extras/models/configs.py:100 +#: netbox/dcim/models/cables.py:680 +msgid "" +"Invalid cable termination: the assigned termination object does not exist." +msgstr "" + +#: netbox/dcim/models/cables.py:748 netbox/extras/models/configs.py:100 msgid "is active" msgstr "" -#: netbox/dcim/models/cables.py:749 +#: netbox/dcim/models/cables.py:752 msgid "is complete" msgstr "" -#: netbox/dcim/models/cables.py:753 +#: netbox/dcim/models/cables.py:756 msgid "is split" msgstr "" -#: netbox/dcim/models/cables.py:766 +#: netbox/dcim/models/cables.py:769 msgid "cable path" msgstr "" -#: netbox/dcim/models/cables.py:767 +#: netbox/dcim/models/cables.py:770 msgid "cable paths" msgstr "" -#: netbox/dcim/models/cables.py:854 +#: netbox/dcim/models/cables.py:857 msgid "All originating terminations must be attached to the same link" msgstr "" -#: netbox/dcim/models/cables.py:872 +#: netbox/dcim/models/cables.py:875 msgid "All mid-span terminations must have the same termination type" msgstr "" -#: netbox/dcim/models/cables.py:880 +#: netbox/dcim/models/cables.py:883 msgid "All mid-span terminations must have the same parent object" msgstr "" -#: netbox/dcim/models/cables.py:910 +#: netbox/dcim/models/cables.py:913 msgid "All links must be cable or wireless" msgstr "" -#: netbox/dcim/models/cables.py:912 +#: netbox/dcim/models/cables.py:915 msgid "All links must match first link type" msgstr "" @@ -7408,24 +7418,24 @@ msgstr "" msgid "Location must be from the same site, {site}." msgstr "" -#: netbox/dcim/models/racks.py:712 +#: netbox/dcim/models/racks.py:709 msgid "units" msgstr "" -#: netbox/dcim/models/racks.py:747 +#: netbox/dcim/models/racks.py:744 msgid "rack reservation" msgstr "" -#: netbox/dcim/models/racks.py:748 +#: netbox/dcim/models/racks.py:745 msgid "rack reservations" msgstr "" -#: netbox/dcim/models/racks.py:762 +#: netbox/dcim/models/racks.py:759 #, python-brace-format msgid "Invalid unit(s) for {height}U rack: {unit_list}" msgstr "" -#: netbox/dcim/models/racks.py:775 +#: netbox/dcim/models/racks.py:772 #, python-brace-format msgid "The following units have already been reserved: {unit_list}" msgstr "" @@ -8612,17 +8622,22 @@ msgstr "" msgid "Show your personal bookmarks" msgstr "" -#: netbox/extras/events.py:194 +#: netbox/extras/events.py:130 +#, python-brace-format +msgid "Cannot enqueue an event for an unsaved {app_label}.{model} instance." +msgstr "" + +#: netbox/extras/events.py:200 #, python-brace-format msgid "Ignoring invalid action_data on event rule \"{rule}\" (got {data_type})" msgstr "" -#: netbox/extras/events.py:270 +#: netbox/extras/events.py:276 #, python-brace-format msgid "Unknown action type for an event rule: {action_type}" msgstr "" -#: netbox/extras/events.py:313 +#: netbox/extras/events.py:319 #, python-brace-format msgid "Cannot import events pipeline {name} error: {error}" msgstr "" @@ -9702,44 +9717,44 @@ msgstr "" msgid "Base set of predefined choices (optional)" msgstr "" -#: netbox/extras/models/customfields.py:894 +#: netbox/extras/models/customfields.py:890 msgid "Choices are automatically ordered alphabetically" msgstr "" -#: netbox/extras/models/customfields.py:901 +#: netbox/extras/models/customfields.py:897 msgid "custom field choice set" msgstr "" -#: netbox/extras/models/customfields.py:902 +#: netbox/extras/models/customfields.py:898 msgid "custom field choice sets" msgstr "" -#: netbox/extras/models/customfields.py:962 +#: netbox/extras/models/customfields.py:958 msgid "Must define base or extra choices." msgstr "" -#: netbox/extras/models/customfields.py:968 +#: netbox/extras/models/customfields.py:964 msgid "Color mappings must be defined as a JSON object." msgstr "" -#: netbox/extras/models/customfields.py:980 +#: netbox/extras/models/customfields.py:976 #, python-brace-format msgid "Duplicate value '{value}' found in extra choices." msgstr "" -#: netbox/extras/models/customfields.py:997 +#: netbox/extras/models/customfields.py:993 #, python-brace-format msgid "" "Color mappings must reference an existing choice value. Invalid value(s): " "{values}." msgstr "" -#: netbox/extras/models/customfields.py:1004 +#: netbox/extras/models/customfields.py:1000 #, python-brace-format msgid "Invalid color value(s): {colors}. Use a supported named color." msgstr "" -#: netbox/extras/models/customfields.py:1027 +#: netbox/extras/models/customfields.py:1023 #, python-brace-format msgid "" "Cannot remove choice {choice} as there are {model} objects which reference " @@ -12503,6 +12518,13 @@ msgstr "" msgid "Lookup" msgstr "" +#: netbox/netbox/models/deletion.py:76 +#, python-brace-format +msgid "" +"{object_name} object can't be deleted because its {pk_attname} attribute is " +"set to None." +msgstr "" + #: netbox/netbox/models/features.py:311 #, python-brace-format msgid "Invalid value for custom field '{name}': {error}" @@ -13303,56 +13325,61 @@ msgstr "" msgid "Imported {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:812 +#: netbox/netbox/views/generic/bulk_views.py:736 +#, python-brace-format +msgid "Custom field form field name must begin with 'cf_': {name}" +msgstr "" + +#: netbox/netbox/views/generic/bulk_views.py:815 #, python-brace-format msgid "Bulk edit {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:828 +#: netbox/netbox/views/generic/bulk_views.py:831 #, python-brace-format msgid "Updated {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:861 -#: netbox/netbox/views/generic/bulk_views.py:1140 -#: netbox/netbox/views/generic/bulk_views.py:1188 +#: netbox/netbox/views/generic/bulk_views.py:864 +#: netbox/netbox/views/generic/bulk_views.py:1143 +#: netbox/netbox/views/generic/bulk_views.py:1191 #, python-brace-format msgid "No {object_type} were selected." msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:968 +#: netbox/netbox/views/generic/bulk_views.py:971 msgid "Select at least one field to rename." msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:998 +#: netbox/netbox/views/generic/bulk_views.py:1001 #, python-brace-format msgid "Renamed {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1069 +#: netbox/netbox/views/generic/bulk_views.py:1072 #, python-brace-format msgid "Bulk delete {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1096 +#: netbox/netbox/views/generic/bulk_views.py:1099 #, python-brace-format msgid "Deleted {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1113 +#: netbox/netbox/views/generic/bulk_views.py:1116 msgid "Deletion failed due to the presence of one or more dependent objects." msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1201 +#: netbox/netbox/views/generic/bulk_views.py:1204 #, python-brace-format msgid "Bulk add {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1254 +#: netbox/netbox/views/generic/bulk_views.py:1257 msgid "An integrity error occurred while creating components" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1266 +#: netbox/netbox/views/generic/bulk_views.py:1269 #, python-brace-format msgid "Added {count} {component} to {parent_count} {parent}." msgstr "" @@ -16267,7 +16294,7 @@ msgstr "" msgid "Related object not found using the provided numeric ID: {id}" msgstr "" -#: netbox/utilities/choices.py:24 +#: netbox/utilities/choices.py:26 #, python-brace-format msgid "{name} has a key defined but CHOICES is not a list" msgstr "" From cdde9e98fa2921413a7faef9d4c476b2bc9dd0ad Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Thu, 4 Jun 2026 17:45:06 +0200 Subject: [PATCH 22/58] test(api): Add GraphQL nested filter and auto-filter tests Introduce declarative GraphQL filter test framework with `GraphQLFilterTest` and `GraphQLQueryTest` dataclasses. Implement auto-filter discovery from filter class annotations with per-field-kind test generators for string, numeric, date, range, and array lookups. Fixes #15569 --- netbox/dcim/tests/query_counts.json | 2 +- netbox/dcim/tests/test_api.py | 73 +- netbox/netbox/tests/test_graphql.py | 68 +- netbox/utilities/testing/api.py | 786 ++++++++++++++++++++- netbox/utilities/tests/test_api_graphql.py | 260 +++++++ 5 files changed, 1165 insertions(+), 24 deletions(-) create mode 100644 netbox/utilities/tests/test_api_graphql.py diff --git a/netbox/dcim/tests/query_counts.json b/netbox/dcim/tests/query_counts.json index 1d7cdda72..7861ee6b7 100644 --- a/netbox/dcim/tests/query_counts.json +++ b/netbox/dcim/tests/query_counts.json @@ -72,7 +72,7 @@ "rearporttemplate:api_list_objects": 12, "region:api_list_objects": 13, "region:list_objects_with_permission": 20, - "site:api_list_objects": 16, + "site:api_list_objects": 17, "site:list_objects_with_permission": 22, "sitegroup:api_list_objects": 13, "sitegroup:list_objects_with_permission": 20, diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 63f968705..a9df054d8 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -20,6 +20,8 @@ from users.models import ObjectPermission, Token, User from utilities.testing import ( APITestCase, APIViewTestCases, + GraphQLFilterTest, + GraphQLQueryTest, create_test_device, create_test_nat_ip_pair, disable_logging, @@ -146,6 +148,19 @@ class SiteTestCase(APIViewTestCases.APIViewTestCase): bulk_update_data = { 'status': 'planned', } + graphql_filter_tests = ( + GraphQLFilterTest( + name='tenant__name__exact', + filters='tenant: {name: {exact: "Tenant 1"}}', + expected=lambda qs: qs.filter(tenant__name='Tenant 1'), + permissions=('tenancy.view_tenant',), + ), + ) + + def assert_nested_locations_active(self, data): + site_data = data.get('site') or {} + location_names = sorted(location['name'] for location in site_data.get('locations', [])) + self.assertEqual(location_names, ['Site1 Active A', 'Site1 Active B']) @classmethod def setUpTestData(cls): @@ -160,15 +175,32 @@ class SiteTestCase(APIViewTestCases.APIViewTestCase): SiteGroup.objects.create(name='Site Group 2', slug='site-group-2'), ) + tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1') + + # Site 1's tenant activates the dynamic tenant prefetch (+1 in api_list_objects baseline). sites = ( - Site(region=regions[0], group=groups[0], name='Site 1', slug='site-1'), + Site(region=regions[0], group=groups[0], tenant=tenant, name='Site 1', slug='site-1'), Site(region=regions[0], group=groups[0], name='Site 2', slug='site-2'), Site(region=regions[0], group=groups[0], name='Site 3', slug='site-3'), ) Site.objects.bulk_create(sites) + nested_site = Site.objects.get(slug='site-1') + cls.nested_site_pk = nested_site.pk + Location.objects.create( + site=nested_site, name='Site1 Active A', slug='site1-active-a', + status=LocationStatusChoices.STATUS_ACTIVE, + ) + Location.objects.create( + site=nested_site, name='Site1 Active B', slug='site1-active-b', + status=LocationStatusChoices.STATUS_ACTIVE, + ) + Location.objects.create( + site=nested_site, name='Site1 Planned', slug='site1-planned', + status=LocationStatusChoices.STATUS_PLANNED, + ) + rir = RIR.objects.create(name='RFC 6996', is_private=True) - tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1') asns = [ ASN(asn=65000 + i, rir=rir) for i in range(8) @@ -203,6 +235,19 @@ class SiteTestCase(APIViewTestCases.APIViewTestCase): }, ] + cls.graphql_query_tests = ( + GraphQLQueryTest( + name='nested_locations_by_status', + query=( + '{ site(id: ' + str(cls.nested_site_pk) + ') { ' + 'locations(filters: {status: {exact: STATUS_ACTIVE}}) { name } ' + '} }' + ), + assert_result=cls.assert_nested_locations_active, + permissions=('dcim.view_location',), + ), + ) + def test_add_tags(self): """ Add tags to an existing object via the add_tags field. @@ -427,6 +472,16 @@ class LocationTestCase(APIViewTestCases.APIViewTestCase): 'description': 'New description', } user_permissions = ('dcim.view_site',) + graphql_filter_tests = ( + GraphQLFilterTest( + name='status__in_list', + filters='status: {in_list: [STATUS_PLANNED, STATUS_STAGING]}', + expected=lambda qs: qs.filter(status__in=[ + LocationStatusChoices.STATUS_PLANNED, + LocationStatusChoices.STATUS_STAGING, + ]), + ), + ) @classmethod def setUpTestData(cls): @@ -476,6 +531,20 @@ class LocationTestCase(APIViewTestCases.APIViewTestCase): parent=parent_locations[0], status=LocationStatusChoices.STATUS_ACTIVE, ) + Location.objects.create( + site=sites[0], + name='GraphQL Planned Location', + slug='graphql-planned-location', + parent=parent_locations[0], + status=LocationStatusChoices.STATUS_PLANNED, + ) + Location.objects.create( + site=sites[0], + name='GraphQL Staging Location', + slug='graphql-staging-location', + parent=parent_locations[0], + status=LocationStatusChoices.STATUS_STAGING, + ) cls.create_data = [ { diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index 2df3a7f98..f960890f7 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -10,7 +10,7 @@ from strawberry.schema.config import StrawberryConfig from dcim.choices import LocationStatusChoices from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Site, VirtualChassis -from extras.models import TableConfig +from extras.models import TableConfig, Tag from netbox.graphql.scalars import BigInt, BigIntScalar from netbox.graphql.schema import Query, get_schema_extensions from utilities.tables import get_table_for_model @@ -185,6 +185,72 @@ class GraphQLAPITestCase(APITestCase): self.assertNotIn('errors', data) self.assertEqual(len(data['data']['site']['locations']), 0) + @override_settings(LOGIN_REQUIRED=True) + def test_graphql_nested_filter_objects(self): + """ + Test filtering of nested GraphQL object lists. + """ + self.add_permissions('dcim.view_site', 'dcim.view_location', 'extras.view_tag') + + site = Site.objects.create( + name='Nested Filter Site', + slug='nested-filter-site' + ) + + # Location is MPTT-managed; bulk_create skips tree-init hooks. Use per-instance create. + Location.objects.create( + site=site, + name='Nested Active 1', + slug='nested-active-1', + status=LocationStatusChoices.STATUS_ACTIVE, + ) + Location.objects.create( + site=site, + name='Nested Active 2', + slug='nested-active-2', + status=LocationStatusChoices.STATUS_ACTIVE, + ) + Location.objects.create( + site=site, + name='Nested Planned', + slug='nested-planned', + status=LocationStatusChoices.STATUS_PLANNED, + ) + + planned = Tag.objects.create(name='Planned', slug='planned') + production = Tag.objects.create(name='Production', slug='production') + staging = Tag.objects.create(name='Staging', slug='staging') + site.tags.add(planned, production, staging) + + url = reverse('graphql') + query = f""" + {{ + site(id: {site.pk}) {{ + locations(filters: {{status: {{exact: STATUS_ACTIVE}}}}) {{ + name + }} + tags(filters: {{name: {{i_starts_with: "P"}}}}) {{ + name + }} + }} + }} + """ + + response = self.client.post(url, data={'query': query}, format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + + data = json.loads(response.content) + self.assertNotIn('errors', data) + + self.assertEqual( + {location['name'] for location in data['data']['site']['locations']}, + {'Nested Active 1', 'Nested Active 2'} + ) + self.assertEqual( + {tag['name'] for tag in data['data']['site']['tags']}, + {'Planned', 'Production'} + ) + def test_graphql_integer_range_lookup(self): """ Test that range_lookup works for integer fields (e.g. vc_position). Regression test for #20468. diff --git a/netbox/utilities/testing/api.py b/netbox/utilities/testing/api.py index 15383b415..938c6a3b7 100644 --- a/netbox/utilities/testing/api.py +++ b/netbox/utilities/testing/api.py @@ -1,21 +1,50 @@ import copy +import importlib import inspect import json +import types +import typing +from collections.abc import Callable +from dataclasses import dataclass +from decimal import Decimal +import strawberry import strawberry_django from django.conf import settings from django.contrib.contenttypes.models import ContentType +from django.contrib.postgres.fields import ArrayField +from django.db import models from django.test import override_settings from django.urls import reverse +from django.utils.module_loading import import_string from rest_framework import status from rest_framework.test import APIClient from strawberry.types.base import StrawberryList, StrawberryOptional from strawberry.types.lazy_type import LazyType from strawberry.types.union import StrawberryUnion +from strawberry_django import ( + BaseFilterLookup, + ComparisonFilterLookup, + DateFilterLookup, + DatetimeFilterLookup, + FilterLookup, + RangeLookup, + StrFilterLookup, + TimeFilterLookup, +) from core.choices import ObjectChangeActionChoices from core.models import ObjectChange, ObjectType from ipam.graphql.types import IPAddressFamilyType +from netbox.graphql.filter_lookups import ( + ArrayLookup, + BigIntegerLookup, + FloatLookup, + IntegerLookup, + IntegerRangeArrayLookup, + JSONFilter, + TreeNodeFilter, +) from netbox.models.features import ChangeLoggingMixin from users.constants import TOKEN_PREFIX from users.models import ObjectPermission, Token, User @@ -28,9 +57,48 @@ from .utils import disable_logging, disable_warnings, get_random_string __all__ = ( 'APITestCase', 'APIViewTestCases', + 'GraphQLFilterTest', + 'GraphQLQueryTest', ) +@dataclass(frozen=True) +class GraphQLFilterTest: + """ + Declarative GraphQL filter test case for APIViewTestCases.GraphQLTestCase. + + ``filters`` is the raw content to place inside the GraphQL ``filters`` input, + e.g. ``name: {i_contains: "site"}``. + + ``expected`` may be a callable accepting the model queryset, an ORM filter + dict, a queryset, an iterable of model instances, or an iterable of object + IDs. When omitted, the test only asserts that the filter returns at least one + result; this preserves compatibility with the legacy ``graphql_filter`` + attribute. + """ + name: str + filters: str + expected: object = None + permissions: tuple[str, ...] = () + + +@dataclass(frozen=True) +class GraphQLQueryTest: + """ + Declarative GraphQL query test case for model-specific complex queries. + + ``assert_result`` is called as ``assert_result(testcase, data)`` where + ``testcase`` is the running ``GraphQLTestCase`` instance (use it for + ``testcase.assertEqual`` etc.) and ``data`` is the decoded GraphQL + ``data`` object (the inner ``response.json()['data']``, not the full HTTP + response). + """ + name: str + query: str + assert_result: Callable + permissions: tuple[str, ...] = () + + # # REST/GraphQL API Tests # @@ -556,6 +624,21 @@ class APIViewTestCases: message=changelog_message) class GraphQLTestCase(APITestCase): + graphql_auto_filter_tests = True + graphql_auto_filter_exclude = () + + # Cap fields per lookup kind to keep test counts balanced across kinds + # (string fields shouldn't crowd out numeric/date/array fields). + graphql_auto_filter_fields_per_kind = 2 + + # Fail when auto mode is on and no tests were generated. + graphql_auto_filter_required = True + + # Additional explicit-list filter cases as GraphQLFilterTest instances. + graphql_filter_tests = () + + # Additional full-query cases (e.g. nested filters) as GraphQLQueryTest instances. + graphql_query_tests = () def _get_graphql_base_name(self): """ @@ -622,26 +705,627 @@ class APIViewTestCases: return query + @staticmethod + def _graphql_literal(value): + """ + Render a Python value as a GraphQL literal. + """ + if value is None: + return 'null' + if isinstance(value, bool): + return 'true' if value else 'false' + if isinstance(value, (int, float)): + return str(value) + if isinstance(value, Decimal): + return str(float(value)) + if isinstance(value, (list, tuple)): + items = ', '.join( + APIViewTestCases.GraphQLTestCase._graphql_literal(v) for v in value + ) + return f'[{items}]' + if isinstance(value, str): + return json.dumps(value) + + return json.dumps(str(value)) + + def _render_graphql_filter_value(self, params): + """ + Render the legacy graphql_filter dict value to a GraphQL filter value. + """ + if isinstance(params, str): + return params + + if not isinstance(params, dict): + return self._graphql_literal(params) + + lookup = params.get('lookup') + value = params['value'] + + if lookup: + return f'{{{lookup}: {self._graphql_literal(value)}}}' + + return self._graphql_literal(value) + + def _build_graphql_filter_string(self, **filters): + if not filters: + return '' + + filter_expressions = [ + f'{field_name}: {self._render_graphql_filter_value(params)}' + for field_name, params in filters.items() + ] + + return f'(filters: {{{", ".join(filter_expressions)}}})' + def _build_filtered_query(self, name, **filters): """ Create a filtered query: i.e. device_list(filters: {name: {i_contains: "akron"}}){. """ - # TODO: This should be extended to support AND, OR multi-lookups - if filters: - for field_name, params in filters.items(): - lookup = params['lookup'] - value = params['value'] - if lookup: - query = f'{{{lookup}: "{value}"}}' - filter_string = f'{field_name}: {query}' - else: - filter_string = f'{field_name}: "{value}"' - filter_string = f'(filters: {{{filter_string}}})' - else: - filter_string = '' + filter_string = self._build_graphql_filter_string(**filters) return self._build_query_with_filter(name, filter_string) + def _build_graphql_id_list_query(self, name, filters): + filter_string = f'(filters: {{{filters}}})' if filters else '' + selection = 'id' if self._graphql_type_exposes_id() else '__typename' + + return f""" + {{ + {name}{filter_string} {{ + {selection} + }} + }} + """ + + def _graphql_type_exposes_id(self): + """ + Return True when the model's GraphQL type exposes ``id`` as a + queryable selection. Some NetBox types (e.g. Notification, + Subscription) omit ``id`` from the output type; for those, the + assertion path falls back to length-only comparison. + """ + type_class = get_graphql_type_for_model(self.model) + strawberry_definition = getattr(type_class, '__strawberry_definition__', None) + if strawberry_definition is None: + return False + return any(field.name == 'id' for field in strawberry_definition.fields) + + def _get_model_graphql_filter_class(self, model=None): + """ + Return the model's GraphQL filter class, if one follows NetBox's + conventional .graphql.filters.Filter path. ``None`` if + the filter module (or any of its parent packages) is absent or the + class is not present in the module. Import errors originating + inside an existing filter module are re-raised. + """ + model = model or self.model + module_path = f'{model._meta.app_label}.graphql.filters' + class_name = f'{model.__name__}Filter' + + try: + module = importlib.import_module(module_path) + except ModuleNotFoundError as exc: + # Treat both ".graphql.filters" absent and any missing + # parent (e.g. ".graphql" or "") as "no conventional + # filter class". Real ImportErrors from inside an existing + # filter module still propagate. + if exc.name == module_path or module_path.startswith(f'{exc.name}.'): + return None + raise + + return getattr(module, class_name, None) + + def _get_graphql_filter_field_names(self): + """ + Return the names exposed by the model's GraphQL filter input, sourced + only from the conventional .graphql.filters.Filter path. + """ + filter_class = self._get_model_graphql_filter_class() + if filter_class is None: + return set() + + return self._collect_filter_class_annotation_names(filter_class) + + @staticmethod + def _collect_filter_class_annotation_names(filter_class): + field_names = set() + for cls in reversed(getattr(filter_class, '__mro__', ())): + field_names.update( + field_name for field_name in getattr(cls, '__annotations__', {}) + if not field_name.startswith('_') + ) + return field_names + + def _assert_graphql_filter_class_present(self, filter_fields, handwritten_tests=()): + """ + Raise when the model has no discoverable filter class or the class + declares no fields. Skipped when auto-filter generation is disabled, + the per-model opt-out attribute is set, or hand-written (legacy or + explicit) filter tests are declared for the model. + """ + if handwritten_tests: + return + if not getattr(self, 'graphql_auto_filter_required', True): + return + if not getattr(self, 'graphql_auto_filter_tests', True): + return + + label = self.model._meta.label + path = f'{self.model._meta.app_label}.graphql.filters.{self.model.__name__}Filter' + + filter_class = self._get_model_graphql_filter_class() + self.assertIsNotNone( + filter_class, + f'No GraphQL filter class found for {label} at {path}. ' + f'Set graphql_auto_filter_required = False on this test case if intentional.' + ) + self.assertTrue( + filter_fields, + f'GraphQL filter class for {label} declares no fields. ' + f'Set graphql_auto_filter_required = False on this test case if intentional.' + ) + + def _get_nonempty_field_value(self, field): + queryset = self._get_queryset() + + if getattr(field, 'null', False): + queryset = queryset.exclude(**{f'{field.name}__isnull': True}) + + if isinstance(field, (models.CharField, models.TextField)): + queryset = queryset.exclude(**{field.name: ''}) + + return queryset.values_list(field.name, flat=True).first() + + def _get_model_field_for_filter_field(self, field_name): + """ + Find the Django model field matching a filter field name. Filter + fields are declared with either the model field name (e.g. `name`) + or the FK attname (e.g. `tenant_id`). + """ + for field in self.model._meta.fields: + if field.name == field_name or getattr(field, 'attname', None) == field_name: + return field + return None + + def _iter_filter_class_annotations(self, filter_class): + """ + Yield (field_name, annotation) pairs for the filter class, walking + its MRO so inherited fields surface. Subclass annotations override + inherited ones (private `_`-prefixed names are skipped). + """ + annotations = {} + for cls in reversed(filter_class.__mro__): + annotations.update({ + name: ann for name, ann in getattr(cls, '__annotations__', {}).items() + if not name.startswith('_') + }) + yield from annotations.items() + + @staticmethod + def _unwrap_filter_annotation(annotation): + """ + Strip ``X | None`` / ``Optional[X]`` and ``Annotated[X, ...]`` + layers. Resolve `strawberry.lazy('...')` metadata so lazily-annotated + lookup types (e.g. ``Annotated['FloatLookup', strawberry.lazy('mod')] | None``) + are returned as the actual class. When an ``Annotated`` layer carries + multiple metadata entries, the first ``module``-bearing entry wins. + Returns None when the inner type cannot be resolved. + """ + if annotation is None: + return None + + lazy_module = None + # Cap iterations at 8: typical NetBox annotations nest at most 3 layers + # (Union > Annotated > ForwardRef). 8 is a generous safety net to + # prevent infinite loops on pathological / future annotation shapes. + for _ in range(8): + origin = typing.get_origin(annotation) + args = typing.get_args(annotation) + + if origin in (typing.Union, types.UnionType): + non_none = [a for a in args if a is not type(None)] + if len(non_none) != 1: + return None + annotation = non_none[0] + continue + + if hasattr(annotation, '__metadata__'): + for meta in annotation.__metadata__: + module_name = getattr(meta, 'module', None) + if module_name: + lazy_module = module_name + break + inner = args[0] if args else None + if inner is None: + return None + annotation = inner + continue + + break + + if isinstance(annotation, (str, typing.ForwardRef)): + if lazy_module is None: + return None + name = annotation.__forward_arg__ if isinstance(annotation, typing.ForwardRef) else annotation + try: + return import_string(f'{lazy_module}.{name}') + except ImportError: + return None + + return annotation + + @classmethod + def _classify_filter_annotation(cls, annotation): + """ + Resolve a filter field annotation to a (kind, kind_arg) tuple keyed + on the declared GraphQL lookup type. Returns (None, None) for + annotations the dispatcher does not handle (those fields are + silently skipped). + """ + annotation = cls._unwrap_filter_annotation(annotation) + if annotation is None or isinstance(annotation, str): + return None, None + + if annotation is strawberry.ID: + return 'id', None + + origin = typing.get_origin(annotation) + target = origin if isinstance(origin, type) else annotation + type_args = typing.get_args(annotation) + + if not isinstance(target, type): + return None, None + + if target in (IntegerLookup, BigIntegerLookup, FloatLookup): + return 'numeric', target + + # TreeNodeFilter schema requires {id, match_type}; skip auto-emit. + if target is TreeNodeFilter: + return None, None + + if issubclass(target, (DateFilterLookup, DatetimeFilterLookup, TimeFilterLookup)): + return 'date_lookup', None + + if target is RangeLookup or issubclass(target, RangeLookup): + return 'range_lookup', type_args[0] if type_args else None + + if issubclass(target, ArrayLookup): + return 'array_lookup', None + if target is IntegerRangeArrayLookup or issubclass(target, IntegerRangeArrayLookup): + return 'range_array_lookup', None + if target is JSONFilter: + # JSONFilter requires explicit (path, typed lookup); no general auto shape. + return None, None + + if issubclass(target, StrFilterLookup): + return 'str_lookup', None + if issubclass(target, ComparisonFilterLookup): + return 'comparison_lookup', type_args[0] if type_args else None + if issubclass(target, FilterLookup): + return 'filter_lookup', type_args[0] if type_args else None + # Enum-typed BaseFilterLookup needs an enum literal; skip auto-emit. + if issubclass(target, BaseFilterLookup): + return None, None + + return None, None + + def _emit_id_filter_tests(self, field_name, _kind_arg): + if field_name == 'id': + instance = self._get_queryset().first() + if instance is None: + return + yield GraphQLFilterTest( + name='id__exact', + filters=f'id: {self._graphql_literal(str(instance.pk))}', + expected=lambda qs, pk=instance.pk: qs.filter(pk=pk), + ) + return + + model_field = self._get_model_field_for_filter_field(field_name) + if model_field is None or not isinstance(model_field, models.ForeignKey): + return + queryset = self._get_queryset().exclude(**{f'{model_field.name}__isnull': True}) + value = queryset.values_list(model_field.attname, flat=True).first() + if value is None: + return + yield GraphQLFilterTest( + name=f'{field_name}__exact', + filters=f'{field_name}: {self._graphql_literal(str(value))}', + expected=lambda qs, attname=model_field.attname, v=value: qs.filter(**{attname: v}), + ) + + def _emit_str_lookup_filter_tests(self, field_name, _kind_arg): + model_field = self._get_model_field_for_filter_field(field_name) + if model_field is None: + return + value = self._get_nonempty_field_value(model_field) + if value in (None, ''): + return + value = str(value) + token = max(1, min(3, len(value))) + lookups = ( + ('exact', 'exact', value), + ('i_contains', 'icontains', value[:token]), + ('i_starts_with', 'istartswith', value[:token]), + ('i_ends_with', 'iendswith', value[-token:]), + ) + for lookup, orm_lookup, filter_value in lookups: + yield GraphQLFilterTest( + name=f'{field_name}__{lookup}', + filters=f'{field_name}: {{{lookup}: {self._graphql_literal(filter_value)}}}', + expected=( + lambda qs, fn=model_field.name, ol=orm_lookup, v=filter_value: + qs.filter(**{f'{fn}__{ol}': v}) + ), + ) + + def _emit_filter_lookup_filter_tests(self, field_name, type_arg): + model_field = self._get_model_field_for_filter_field(field_name) + if model_field is None: + return + value = self._get_nonempty_field_value(model_field) + if value is None: + return + if type_arg is bool or isinstance(value, bool): + yield GraphQLFilterTest( + name=f'{field_name}__exact', + filters=f'{field_name}: {{exact: {self._graphql_literal(value)}}}', + expected=lambda qs, fn=model_field.name, v=value: qs.filter(**{fn: v}), + ) + return + yield GraphQLFilterTest( + name=f'{field_name}__exact', + filters=f'{field_name}: {{exact: {self._graphql_literal(value)}}}', + expected=lambda qs, fn=model_field.name, v=value: qs.filter(**{f'{fn}__exact': v}), + ) + + def _emit_comparison_lookup_filter_tests(self, field_name, _type_arg): + model_field = self._get_model_field_for_filter_field(field_name) + if model_field is None: + return + value = self._get_nonempty_field_value(model_field) + if value is None: + return + yield GraphQLFilterTest( + name=f'{field_name}__exact', + filters=f'{field_name}: {{exact: {self._graphql_literal(value)}}}', + expected=lambda qs, fn=model_field.name, v=value: qs.filter(**{f'{fn}__exact': v}), + ) + + def _emit_numeric_filter_tests(self, field_name, _type_arg): + # NetBox numeric wrapper: {filter_lookup: {exact: N}}. + model_field = self._get_model_field_for_filter_field(field_name) + if model_field is None: + return + if isinstance(model_field, ArrayField): + return + value = self._get_nonempty_field_value(model_field) + if value is None: + return + if isinstance(value, Decimal): + value = float(value) + yield GraphQLFilterTest( + name=f'{field_name}__filter_lookup__exact', + filters=( + f'{field_name}: {{filter_lookup: ' + f'{{exact: {self._graphql_literal(value)}}}}}' + ), + expected=lambda qs, fn=model_field.name, v=value: qs.filter(**{f'{fn}__exact': v}), + ) + + def _emit_date_lookup_filter_tests(self, field_name, _kind_arg): + model_field = self._get_model_field_for_filter_field(field_name) + if model_field is None: + return + value = self._get_nonempty_field_value(model_field) + if value is None: + return + iso_value = value.isoformat() if hasattr(value, 'isoformat') else str(value) + yield GraphQLFilterTest( + name=f'{field_name}__exact', + filters=f'{field_name}: {{exact: "{iso_value}"}}', + expected=lambda qs, fn=model_field.name, v=value: qs.filter(**{fn: v}), + ) + + def _emit_range_lookup_filter_tests(self, field_name, _kind_arg): + model_field = self._get_model_field_for_filter_field(field_name) + if model_field is None: + return + aggregates = self._get_queryset().aggregate( + _min=models.Min(model_field.name), _max=models.Max(model_field.name), + ) + start, end = aggregates['_min'], aggregates['_max'] + if start is None or end is None or start == end: + return + yield GraphQLFilterTest( + name=f'{field_name}__range_lookup', + filters=( + f'{field_name}: {{range_lookup: ' + f'{{start: {self._graphql_literal(start)}, end: {self._graphql_literal(end)}}}}}' + ), + expected=( + lambda qs, fn=model_field.name, lo=start, hi=end: + qs.filter(**{f'{fn}__gte': lo, f'{fn}__lte': hi}) + ), + ) + + def _emit_array_lookup_filter_tests(self, field_name, _kind_arg): + model_field = self._get_model_field_for_filter_field(field_name) + if model_field is None: + return + if not isinstance(model_field, ArrayField): + return + queryset = self._get_queryset().exclude(**{field_name: []}) + sample = queryset.values_list(field_name, flat=True).first() + if not sample: + return + element = sample[0] + yield GraphQLFilterTest( + name=f'{field_name}__contains', + filters=( + f'{field_name}: {{contains: [{self._graphql_literal(element)}]}}' + ), + expected=( + lambda qs, fn=model_field.name, v=element: qs.filter(**{f'{fn}__contains': [v]}) + ), + ) + + def _emit_range_array_lookup_filter_tests(self, field_name, _kind_arg): + model_field = self._get_model_field_for_filter_field(field_name) + if model_field is None: + return + queryset = self._get_queryset().exclude(**{f'{field_name}__isnull': True}) + sample = queryset.values_list(field_name, flat=True).first() + if not sample: + return + first_range = sample[0] + lower = getattr(first_range, 'lower', None) + if lower is None: + return + yield GraphQLFilterTest( + name=f'{field_name}__contains', + filters=f'{field_name}: {{contains: {self._graphql_literal(lower)}}}', + expected=( + lambda qs, fn=model_field.name, v=lower: qs.filter(**{f'{fn}__range_contains': v}) + ), + ) + + def _iter_auto_graphql_filter_tests(self): + if not getattr(self, 'graphql_auto_filter_tests', True): + return + + filter_class = self._get_model_graphql_filter_class() + if filter_class is None: + return + + exclude = set(getattr(self, 'graphql_auto_filter_exclude', ())) + per_kind = self.graphql_auto_filter_fields_per_kind + + # Bucket eligible fields by lookup kind so per-kind budgeting balances coverage. + by_kind: dict[str, list[tuple[str, object]]] = {} + for field_name, annotation in self._iter_filter_class_annotations(filter_class): + if field_name in exclude: + continue + kind, kind_arg = self._classify_filter_annotation(annotation) + if kind is None: + continue + by_kind.setdefault(kind, []).append((field_name, kind_arg)) + + # Emit per-kind; the cap counts SUCCESSFUL emissions, not candidate fields, so + # early null/empty fields don't shadow later fields with usable fixture data. + for kind, fields in by_kind.items(): + emitter = getattr(self, f'_emit_{kind}_filter_tests', None) + if emitter is None: + continue + + emitted_fields = 0 + for field_name, kind_arg in fields: + tests = list(emitter(field_name, kind_arg)) + if not tests: + continue + yield from tests + emitted_fields += 1 + if emitted_fields >= per_kind: + break + + def _iter_legacy_graphql_filter_tests(self): + if not hasattr(self, 'graphql_filter'): + return + + filter_expressions = [ + f'{field_name}: {self._render_graphql_filter_value(params)}' + for field_name, params in self.graphql_filter.items() + ] + + yield GraphQLFilterTest( + name='graphql_filter', + filters=', '.join(filter_expressions), + ) + + def _coerce_graphql_filter_test(self, filter_test): + if isinstance(filter_test, GraphQLFilterTest): + return filter_test + + filter_test = dict(filter_test) + if 'filter' in filter_test and 'filters' not in filter_test: + filter_test['filters'] = filter_test.pop('filter') + + return GraphQLFilterTest(**filter_test) + + def _iter_explicit_graphql_filter_tests(self): + for filter_test in getattr(self, 'graphql_filter_tests', ()): + yield self._coerce_graphql_filter_test(filter_test) + + def _get_expected_id_set(self, filter_test): + expected = filter_test.expected + + if callable(expected): + expected = expected(self._get_queryset()) + + if isinstance(expected, dict): + expected = self._get_queryset().filter(**expected) + + if hasattr(expected, 'values_list'): + values = expected.distinct().values_list('pk', flat=True) + else: + values = [getattr(value, 'pk', value) for value in expected] + + return {str(value) for value in values} + + def _assert_graphql_filter_test(self, url, field_name, filter_test): + query = self._build_graphql_id_list_query(field_name, filter_test.filters) + + for permission in filter_test.permissions: + self.add_permissions(permission) + + response = self.client.post(url, data={'query': query}, format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + + data = json.loads(response.content) + self.assertNotIn('errors', data) + + results = data['data'][field_name] + + if filter_test.expected is None: + self.assertGreater(len(results), 0) + return + + expected_ids = self._get_expected_id_set(filter_test) + + self.assertGreater( + len(expected_ids), 0, + msg=( + f'{self.model._meta.label}: filter "{filter_test.name}" produced an empty ' + f'expected set; the test would tautologically pass. Adjust fixtures or the ' + f'filter so the expected ORM queryset is non-empty.' + ), + ) + + if self._graphql_type_exposes_id(): + result_ids = [str(result['id']) for result in results] + self.assertEqual( + set(result_ids), expected_ids, + msg=f'{self.model._meta.label}: filter "{filter_test.name}" ID set mismatch', + ) + + self.assertEqual( + len(results), len(expected_ids), + msg=( + f'{self.model._meta.label}: filter "{filter_test.name}" result count mismatch ' + f'(GraphQL type does not expose id; comparing by length).' + ), + ) + + def _coerce_graphql_query_test(self, query_test): + if isinstance(query_test, GraphQLQueryTest): + return query_test + + query_test = dict(query_test) + if 'assertion' in query_test and 'assert_result' not in query_test: + query_test['assert_result'] = query_test.pop('assertion') + + return GraphQLQueryTest(**query_test) + def _build_query(self, name, **filters): """ Create a normal query - unfiltered or with a string query: i.e. site(name: "aaa"){. @@ -740,14 +1424,44 @@ class APIViewTestCases: self.assertNotIn('errors', data) self.assertEqual(len(data['data'][field_name]), self.model.objects.count()) + def _assert_graphql_filter_tests_exist(self, auto_tests, legacy_tests, explicit_tests): + """ + Fail loudly when auto mode is required and no GraphQL filter tests + (auto, legacy, or explicit) exist for the current model. + """ + if ( + getattr(self, 'graphql_auto_filter_tests', True) + and getattr(self, 'graphql_auto_filter_required', True) + and not auto_tests + and not legacy_tests + and not explicit_tests + ): + self.fail( + f'No GraphQL filter tests were generated for {self.model._meta.label}. ' + f'Set graphql_auto_filter_required = False or add explicit graphql_filter_tests ' + f'if intentional.' + ) + @override_settings(LOGIN_REQUIRED=True) def test_graphql_filter_objects(self): - if not hasattr(self, 'graphql_filter'): + legacy_tests = list(self._iter_legacy_graphql_filter_tests()) + explicit_tests = list(self._iter_explicit_graphql_filter_tests()) + + filter_fields = self._get_graphql_filter_field_names() + self._assert_graphql_filter_class_present( + filter_fields, handwritten_tests=[*legacy_tests, *explicit_tests] + ) + + auto_tests = list(self._iter_auto_graphql_filter_tests()) + + self._assert_graphql_filter_tests_exist(auto_tests, legacy_tests, explicit_tests) + + filter_tests = [*auto_tests, *legacy_tests, *explicit_tests] + if not filter_tests: return url = reverse('graphql') field_name = f'{self._get_graphql_base_name()}_list' - query = self._build_filtered_query(field_name, **self.graphql_filter) # Add object-level permission obj_perm = ObjectPermission( @@ -758,11 +1472,43 @@ class APIViewTestCases: obj_perm.users.add(self.user) obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model)) - response = self.client.post(url, data={'query': query}, format="json", **self.header) - self.assertHttpStatus(response, status.HTTP_200_OK) - data = json.loads(response.content) - self.assertNotIn('errors', data) - self.assertGreater(len(data['data'][field_name]), 0) + for filter_test in filter_tests: + with self.subTest(filter=filter_test.name): + self._assert_graphql_filter_test(url, field_name, filter_test) + + @override_settings(LOGIN_REQUIRED=True) + def test_graphql_extra_queries(self): + query_tests = [ + self._coerce_graphql_query_test(query_test) + for query_test in getattr(self, 'graphql_query_tests', ()) + ] + + if not query_tests: + return + + url = reverse('graphql') + + # Add object-level permission for this model. Additional permissions + # required by the query can be declared on the GraphQLQueryTest. + obj_perm = ObjectPermission( + name='Test permission', + actions=['view'] + ) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model)) + + for query_test in query_tests: + with self.subTest(query=query_test.name): + for permission in query_test.permissions: + self.add_permissions(permission) + + response = self.client.post(url, data={'query': query_test.query}, format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + + data = json.loads(response.content) + self.assertNotIn('errors', data) + query_test.assert_result(self, data['data']) class APIViewTestCase( GetObjectViewTestCase, diff --git a/netbox/utilities/tests/test_api_graphql.py b/netbox/utilities/tests/test_api_graphql.py new file mode 100644 index 000000000..a9a368a28 --- /dev/null +++ b/netbox/utilities/tests/test_api_graphql.py @@ -0,0 +1,260 @@ +"""Tests for the GraphQL filter test framework in utilities/testing/api.py.""" + +import sys +import types +from decimal import Decimal +from typing import Annotated + +import strawberry +from django.test import TestCase + +from netbox.graphql.filter_lookups import FloatLookup +from utilities.testing.api import APIViewTestCases, GraphQLFilterTest + + +class GraphQLFilterAnnotationMROTestCase(TestCase): + """Cover MRO override, import error propagation, lazy annotation resolution, and the zero-auto-test gate.""" + + def test_subclass_annotation_overrides_base(self): + """Subclass annotations win over base in `_iter_filter_class_annotations`.""" + class Base: + __annotations__ = {'shared': int} + + class Sub(Base): + __annotations__ = {'shared': str} + + # Stand up a throwaway instance just to access the method as bound. + instance = APIViewTestCases.GraphQLTestCase() + pairs = dict(instance._iter_filter_class_annotations(Sub)) + self.assertEqual(pairs['shared'], str) + + def test_get_filter_class_propagates_real_import_errors(self): + """A broken import inside a model's filters module must surface, not silently return None.""" + broken = types.ModuleType('netbox_broken_filter_fixture.graphql.filters') + + def _raise(*args, **kwargs): + raise ImportError('simulated downstream breakage') + + broken.__getattr__ = _raise + # sys.modules mutation is safe under --parallel (separate processes), not threads. + sys.modules['netbox_broken_filter_fixture'] = types.ModuleType('netbox_broken_filter_fixture') + sys.modules['netbox_broken_filter_fixture.graphql'] = types.ModuleType( + 'netbox_broken_filter_fixture.graphql' + ) + sys.modules['netbox_broken_filter_fixture.graphql.filters'] = broken + + try: + class FakeMeta: + app_label = 'netbox_broken_filter_fixture' + + class FakeModel: + _meta = FakeMeta() + __name__ = 'BrokenModel' + + instance = APIViewTestCases.GraphQLTestCase() + with self.assertRaises(ImportError): + instance._get_model_graphql_filter_class(FakeModel) + finally: + for key in ( + 'netbox_broken_filter_fixture', + 'netbox_broken_filter_fixture.graphql', + 'netbox_broken_filter_fixture.graphql.filters', + ): + sys.modules.pop(key, None) + + def test_zero_auto_filter_tests_fails_loudly(self): + """Helper fails when auto mode is required and no tests of any kind exist.""" + + class FakeMeta: + label = 'fake.FakeModel' + + class FakeModel: + _meta = FakeMeta() + + class Case(APIViewTestCases.GraphQLTestCase): + model = FakeModel + graphql_auto_filter_tests = True + graphql_auto_filter_required = True + + case = Case() + with self.assertRaisesRegex(AssertionError, r'No GraphQL filter tests.*fake\.FakeModel'): + case._assert_graphql_filter_tests_exist([], [], []) + + def test_lazy_annotated_lookup_resolves(self): + """Annotated['FloatLookup', strawberry.lazy(...)] | None resolves to FloatLookup.""" + annotation = Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None + self.assertIs( + APIViewTestCases.GraphQLTestCase._unwrap_filter_annotation(annotation), + FloatLookup, + ) + + def test_str_lookup_emits_all_four_variants(self): + """`_emit_str_lookup_filter_tests` emits exact, i_contains, i_starts_with, i_ends_with.""" + captured_value = 'production' + + class FakeMeta: + label = 'fake.FakeModel' + app_label = 'fake' + + class Case(APIViewTestCases.GraphQLTestCase): + def _get_model_field_for_filter_field(self, field_name): + class FakeField: + name = field_name + return FakeField() + + def _get_nonempty_field_value(self, field): + return captured_value + + def _graphql_literal(self, value): + return f'"{value}"' + + case = Case() + tests = list(case._emit_str_lookup_filter_tests('name', None)) + self.assertEqual( + [t.name for t in tests], + ['name__exact', 'name__i_contains', 'name__i_starts_with', 'name__i_ends_with'], + ) + + def test_explicit_tests_satisfy_auto_required_gate(self): + """Helper does NOT fail when explicit tests exist, even if auto/legacy are empty.""" + + class FakeMeta: + label = 'fake.FakeModel' + + class FakeModel: + _meta = FakeMeta() + + class Case(APIViewTestCases.GraphQLTestCase): + model = FakeModel + graphql_auto_filter_tests = True + graphql_auto_filter_required = True + + case = Case() + # Should not raise. + case._assert_graphql_filter_tests_exist( + auto_tests=[], + legacy_tests=[], + explicit_tests=[GraphQLFilterTest(name='x', filters='x: 1')], + ) + + def test_graphql_literal_renders_lists(self): + """List and tuple values render as GraphQL list literals, not quoted strings.""" + literal = APIViewTestCases.GraphQLTestCase._graphql_literal + self.assertEqual(literal([1, 2, 3]), '[1, 2, 3]') + self.assertEqual(literal(('a', 'b')), '["a", "b"]') + self.assertEqual(literal([]), '[]') + + def test_graphql_literal_renders_decimal_as_number(self): + """Decimal values render as numeric literals, not quoted strings.""" + literal = APIViewTestCases.GraphQLTestCase._graphql_literal + self.assertEqual(literal(Decimal('1.23')), '1.23') + self.assertEqual(literal([Decimal('1.5'), Decimal('2.5')]), '[1.5, 2.5]') + + def test_per_kind_cap_counts_successful_emissions(self): + """Later candidate fields are tried until per-kind successful emissions reach the cap.""" + + class FakeMeta: + label = 'fake.FakeModel' + app_label = 'fake' + + class FakeModel: + _meta = FakeMeta() + __name__ = 'FakeModel' + + emit_calls = [] + + class Case(APIViewTestCases.GraphQLTestCase): + model = FakeModel + graphql_auto_filter_fields_per_kind = 2 + + def _get_model_graphql_filter_class(self, model=None): + class FilterClass: + __annotations__ = { + 'empty_field_1': str, + 'empty_field_2': str, + 'useful_field_1': str, + 'useful_field_2': str, + 'useful_field_3': str, + } + return FilterClass + + def _classify_filter_annotation(self, annotation): + return 'str_lookup', None + + def _emit_str_lookup_filter_tests(self, field_name, _kind_arg): + emit_calls.append(field_name) + if field_name.startswith('empty_'): + return iter(()) + return iter((GraphQLFilterTest(name=field_name, filters=f'{field_name}: "x"'),)) + + case = Case() + list(case._iter_auto_graphql_filter_tests()) + + # The candidate-counting bug stops at 'empty_field_1' and 'empty_field_2' (the slice + # captures the first 2). After the fix, the emitter is invoked on all 5 candidates + # in order until 2 SUCCESSFUL fields have emitted. + self.assertEqual( + emit_calls, + ['empty_field_1', 'empty_field_2', 'useful_field_1', 'useful_field_2'], + ) + + def test_get_filter_class_returns_none_when_parent_module_missing(self): + """When the parent `.graphql` package is absent, return None instead of re-raising.""" + + class FakeMeta: + app_label = 'netbox_missing_graphql_fixture' + + class FakeModel: + _meta = FakeMeta() + __name__ = 'BrokenModel' + + instance = APIViewTestCases.GraphQLTestCase() + # No `netbox_missing_graphql_fixture` package is registered in sys.modules, + # so import_module raises ModuleNotFoundError with exc.name == 'netbox_missing_graphql_fixture' + # (the parent), not the full path 'netbox_missing_graphql_fixture.graphql.filters'. + # The fix accepts both shapes. + self.assertIsNone(instance._get_model_graphql_filter_class(FakeModel)) + + def test_filter_class_assertion_skipped_with_handwritten_tests(self): + """Hand-written tests exempt a model from the conventional filter class requirement.""" + + class FakeMeta: + label = 'fake.FakeModel' + app_label = 'fake' + + class FakeModel: + _meta = FakeMeta() + __name__ = 'FakeModel' + + class Case(APIViewTestCases.GraphQLTestCase): + model = FakeModel + + def _get_model_graphql_filter_class(self, model=None): + return None + + case = Case() + # Should not raise despite the missing conventional filter class. + case._assert_graphql_filter_class_present( + set(), handwritten_tests=[GraphQLFilterTest(name='x', filters='x: 1')] + ) + + def test_filter_class_assertion_fails_without_filter_class(self): + """Missing conventional filter class raises when no hand-written tests exist.""" + + class FakeMeta: + label = 'fake.FakeModel' + app_label = 'fake' + + class FakeModel: + _meta = FakeMeta() + __name__ = 'FakeModel' + + class Case(APIViewTestCases.GraphQLTestCase): + model = FakeModel + + def _get_model_graphql_filter_class(self, model=None): + return None + + case = Case() + with self.assertRaisesRegex(AssertionError, r'No GraphQL filter class found for fake\.FakeModel'): + case._assert_graphql_filter_class_present(set()) From 553b97464ab84f0b631c8328794839f0048824cc Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 4 Jun 2026 14:55:18 -0400 Subject: [PATCH 23/58] Fixes #22388: Pin redis-py to <8.0 (#22389) --- base_requirements.txt | 5 +++++ requirements.txt | 1 + 2 files changed, 6 insertions(+) diff --git a/base_requirements.txt b/base_requirements.txt index c2d533af8..32fa67e26 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -138,6 +138,11 @@ psycopg[c,pool] # https://github.com/yaml/pyyaml/blob/master/CHANGES PyYAML +# redis-py +# https://github.com/redis/redis-py +# Default protocol changes to RESP3 in v8.0; see #22388 +redis<8.0 + # Requests # https://github.com/psf/requests/blob/main/HISTORY.md requests diff --git a/requirements.txt b/requirements.txt index 4ae44b033..374861243 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,6 +32,7 @@ nh3==0.3.5 Pillow==12.2.0 psycopg[c,pool]==3.3.4 PyYAML==6.0.3 +redis==7.4.0 requests==2.34.2 rq==2.9.0 social-auth-app-django==5.9.0 From d592afe56ccdbf3f34a0696421f1ecf4c6f58880 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 4 Jun 2026 14:57:03 -0400 Subject: [PATCH 24/58] Closes #22349: Correct documentation to reflect minimum Redis version of 5.0 --- docs/installation/2-redis.md | 2 +- docs/installation/index.md | 2 +- docs/installation/upgrading.md | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/installation/2-redis.md b/docs/installation/2-redis.md index c29deb5c2..00a02b889 100644 --- a/docs/installation/2-redis.md +++ b/docs/installation/2-redis.md @@ -8,7 +8,7 @@ sudo apt install -y redis-server ``` -Before continuing, verify that your installed version of Redis is at least v4.0: +Before continuing, verify that your installed version of Redis is at least v5.0: ```no-highlight redis-server -v diff --git a/docs/installation/index.md b/docs/installation/index.md index 326f9e4dd..a20bcd369 100644 --- a/docs/installation/index.md +++ b/docs/installation/index.md @@ -29,7 +29,7 @@ The following sections detail how to set up a new instance of NetBox: |------------|--------------------| | Python | 3.12, 3.13, 3.14 | | PostgreSQL | 14+ [^1] | -| Redis | 4.0+ | +| Redis | 5.0+ | [^1]: Support for PostgreSQL 14 is deprecated and will be removed in NetBox v4.7. PostgreSQL 15 or later will be required. diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md index 64a61b9d8..cc0ec476f 100644 --- a/docs/installation/upgrading.md +++ b/docs/installation/upgrading.md @@ -34,7 +34,7 @@ NetBox requires the following dependencies: |------------|--------------------| | Python | 3.12, 3.13, 3.14 | | PostgreSQL | 14+ [^1] | -| Redis | 4.0+ | +| Redis | 5.0+ | [^1]: Support for PostgreSQL 14 is deprecated and will be removed in NetBox v4.7. PostgreSQL 15 or later will be required. @@ -42,10 +42,10 @@ NetBox requires the following dependencies: | NetBox Version | Python min | Python max | PostgreSQL min | Redis min | Documentation | |:--------------:|:----------:|:----------:|:--------------:|:---------:|:-----------------------------------------------------------------------------------------:| -| 4.6 | 3.12 | 3.14 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.6.0/docs/installation/index.md) | -| 4.5 | 3.12 | 3.14 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.5.0/docs/installation/index.md) | -| 4.4 | 3.10 | 3.12 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.4.0/docs/installation/index.md) | -| 4.3 | 3.10 | 3.12 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.3.0/docs/installation/index.md) | +| 4.6 | 3.12 | 3.14 | 14 | 5.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.6.0/docs/installation/index.md) | +| 4.5 | 3.12 | 3.14 | 14 | 5.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.5.0/docs/installation/index.md) | +| 4.4 | 3.10 | 3.12 | 14 | 5.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.4.0/docs/installation/index.md) | +| 4.3 | 3.10 | 3.12 | 14 | 5.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.3.0/docs/installation/index.md) | | 4.2 | 3.10 | 3.12 | 13 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.2.0/docs/installation/index.md) | | 4.1 | 3.10 | 3.12 | 12 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.1.0/docs/installation/index.md) | | 4.0 | 3.10 | 3.12 | 12 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.0.0/docs/installation/index.md) | From b905e99e6353072d3251092dad0222bd0b1c918f Mon Sep 17 00:00:00 2001 From: Alex Houlton <298057+alexhoulton@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:50:39 +0100 Subject: [PATCH 25/58] Closes #22375: Fix VLAN filter_interface_id performance: use UNION instead of OR across M2M joins (#22387) --- netbox/ipam/filtersets.py | 14 ++++------ netbox/ipam/tests/test_filtersets.py | 41 +++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index bba050215..c95e76d14 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -1142,19 +1142,17 @@ class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet): def filter_interface_id(self, queryset, name, value): if value is None: return queryset.none() - return queryset.filter( - Q(interfaces_as_tagged=value) | - Q(interfaces_as_untagged=value) - ).distinct() + tagged = queryset.filter(interfaces_as_tagged=value) + untagged = queryset.filter(interfaces_as_untagged=value) + return queryset.filter(pk__in=tagged.union(untagged).values('pk')) @extend_schema_field(OpenApiTypes.INT) def filter_vminterface_id(self, queryset, name, value): if value is None: return queryset.none() - return queryset.filter( - Q(vminterfaces_as_tagged=value) | - Q(vminterfaces_as_untagged=value) - ).distinct() + tagged = queryset.filter(vminterfaces_as_tagged=value) + untagged = queryset.filter(vminterfaces_as_untagged=value) + return queryset.filter(pk__in=tagged.union(untagged).values('pk')) @register_filterset diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py index 900436d62..4e396a9f8 100644 --- a/netbox/ipam/tests/test_filtersets.py +++ b/netbox/ipam/tests/test_filtersets.py @@ -4,7 +4,7 @@ from django.test import TestCase from netaddr import IPNetwork from circuits.models import Provider -from dcim.choices import InterfaceTypeChoices +from dcim.choices import InterfaceModeChoices, InterfaceTypeChoices from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup from ipam.choices import * from ipam.filtersets import * @@ -2206,11 +2206,50 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'interface_id': interface_id} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + # An interface untagged on one VLAN and tagged on a different VLAN should return both (UNION across paths) + vlans = self.queryset.all()[:2] + interface = Interface.objects.create( + device=Device.objects.first(), + name='Interface X', + type=InterfaceTypeChoices.TYPE_1GE_FIXED, + mode=InterfaceModeChoices.MODE_TAGGED, + untagged_vlan=vlans[0], + ) + interface.tagged_vlans.add(vlans[1]) + params = {'interface_id': interface.pk} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + # A VLAN that is both untagged and tagged on the same interface should be returned only once (deduplication) + interface.tagged_vlans.add(vlans[0]) + params = {'interface_id': interface.pk} + qs = self.filterset(params, self.queryset).qs + self.assertEqual(qs.count(), 2) + self.assertEqual(len(qs), len(set(qs.values_list('pk', flat=True)))) + def test_vminterface(self): vminterface_id = VMInterface.objects.first().pk params = {'vminterface_id': vminterface_id} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + # A VM interface untagged on one VLAN and tagged on a different VLAN should return both (UNION across paths) + vlans = self.queryset.all()[:2] + vminterface = VMInterface.objects.create( + virtual_machine=VirtualMachine.objects.first(), + name='VM Interface X', + mode=InterfaceModeChoices.MODE_TAGGED, + untagged_vlan=vlans[0], + ) + vminterface.tagged_vlans.add(vlans[1]) + params = {'vminterface_id': vminterface.pk} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + # A VLAN that is both untagged and tagged on the same interface should be returned only once (deduplication) + vminterface.tagged_vlans.add(vlans[0]) + params = {'vminterface_id': vminterface.pk} + qs = self.filterset(params, self.queryset).qs + self.assertEqual(qs.count(), 2) + self.assertEqual(len(qs), len(set(qs.values_list('pk', flat=True)))) + def test_qinq_role(self): params = {'qinq_role': [VLANQinQRoleChoices.ROLE_SERVICE, VLANQinQRoleChoices.ROLE_CUSTOMER]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) From 86ea67d6400aa1300d00b047090c8b7d54bb2dac Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Thu, 4 Jun 2026 19:57:15 +0200 Subject: [PATCH 26/58] fix(extras): Prevent direct access to TableConfig create view Add GET handler to TableConfigEditView that redirects users to home with a warning if they attempt to access the create form directly without required object_type and table parameters from a source list view. Fixes #22237 --- netbox/extras/forms/bulk_edit.py | 2 +- netbox/extras/forms/model_forms.py | 23 ++++++++- netbox/extras/models/models.py | 6 ++- netbox/extras/tests/test_forms.py | 51 ++++++++++++++++++- netbox/extras/tests/test_models.py | 20 ++++++++ netbox/extras/tests/test_views.py | 49 ++++++++++++++++-- netbox/extras/views.py | 8 +++ netbox/templates/extras/tableconfig_edit.html | 5 ++ 8 files changed, 156 insertions(+), 8 deletions(-) diff --git a/netbox/extras/forms/bulk_edit.py b/netbox/extras/forms/bulk_edit.py index 9fecfdb7a..abfe22f5a 100644 --- a/netbox/extras/forms/bulk_edit.py +++ b/netbox/extras/forms/bulk_edit.py @@ -212,7 +212,7 @@ class SavedFilterBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm): nullable_fields = ('description',) -class TableConfigBulkEditForm(BulkEditForm): +class TableConfigBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=TableConfig.objects.all(), widget=forms.MultipleHiddenInput diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index f1b23e0d5..ba995c701 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -402,7 +402,7 @@ class SavedFilterForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm): super().__init__(*args, initial=initial, **kwargs) -class TableConfigForm(forms.ModelForm): +class TableConfigForm(ChangelogMessageMixin, forms.ModelForm): object_type = ContentTypeChoiceField( label=_('Object type'), queryset=ObjectType.objects.all() @@ -438,10 +438,29 @@ class TableConfigForm(forms.ModelForm): def __init__(self, data=None, *args, **kwargs): super().__init__(data, *args, **kwargs) - object_type = ObjectType.objects.get(pk=get_field_value(self, 'object_type')) + self.fields['available_columns'].widget.choices = () + self.fields['columns'].widget.choices = () + + # Table context may be absent e.g. when the add view is requested directly + object_type_pk = get_field_value(self, 'object_type') + object_type_pk = getattr(object_type_pk, 'pk', object_type_pk) + if not object_type_pk: + return + + try: + object_type = ObjectType.objects.get(pk=object_type_pk) + except (ObjectType.DoesNotExist, TypeError, ValueError): + return + model = object_type.model_class() + if model is None: + return + table_name = get_field_value(self, 'table') table_class = get_table_for_model(model, table_name) + if table_class is None: + return + table = table_class([]) if columns := self._get_columns(): diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 14cae8d9d..d538f94bf 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -649,6 +649,10 @@ class TableConfig(CloningMixin, ChangeLoggedModel): def clean(self): super().clean() + # Skip table validation until the object type and table have been set + if not self.object_type_id or not self.table: + return + # Validate table if self.table_class is None: raise ValidationError({ @@ -667,7 +671,7 @@ class TableConfig(CloningMixin, ChangeLoggedModel): }) # Validate selected columns - for name in self.columns: + for name in self.columns or []: if name not in table.columns: raise ValidationError({ 'columns': _('Unknown column: {name}').format(name=name) diff --git a/netbox/extras/tests/test_forms.py b/netbox/extras/tests/test_forms.py index 26de1de80..909d72801 100644 --- a/netbox/extras/tests/test_forms.py +++ b/netbox/extras/tests/test_forms.py @@ -10,7 +10,7 @@ from core.models import DataSource, ObjectType from dcim.forms import SiteForm from dcim.models import Site from extras.choices import CustomFieldTypeChoices -from extras.forms import SavedFilterForm +from extras.forms import SavedFilterForm, TableConfigBulkEditForm, TableConfigForm from extras.forms.model_forms import CustomFieldChoiceSetForm from extras.forms.scripts import ScriptFileForm from extras.models import CustomField, CustomFieldChoiceSet, ScriptModule @@ -288,3 +288,52 @@ class ScriptFileFormTestCase(TestCase): form = ScriptFileForm(files={'upload_file': upload_file}, instance=self._new_module()) self.assertTrue(form.is_valid()) + + +class TableConfigFormTestCase(TestCase): + + def test_form_without_table_context(self): + """The form must be constructible without an object type.""" + form = TableConfigForm() + self.assertEqual(list(form.fields['available_columns'].widget.choices), []) + self.assertEqual(list(form.fields['columns'].widget.choices), []) + + def test_form_with_invalid_object_type(self): + """An unknown object type must yield empty column choices.""" + last_pk = ObjectType.objects.order_by('pk').last().pk + form = TableConfigForm(initial={'object_type': last_pk + 1}) + self.assertEqual(list(form.fields['available_columns'].widget.choices), []) + self.assertEqual(list(form.fields['columns'].widget.choices), []) + + def test_form_with_unknown_table(self): + """An unresolvable table name must yield empty column choices.""" + object_type = ObjectType.objects.get_for_model(Site) + form = TableConfigForm(initial={'object_type': object_type.pk, 'table': 'NoSuchTable'}) + self.assertEqual(list(form.fields['columns'].widget.choices), []) + + def test_form_with_table_context(self): + """Column choices must be populated from the resolved table.""" + object_type = ObjectType.objects.get_for_model(Site) + form = TableConfigForm(initial={ + 'object_type': object_type.pk, + 'table': 'SiteTable', + 'columns': ['name', 'status'], + }) + self.assertEqual( + [name for name, _ in form.fields['columns'].widget.choices], + ['name', 'status'] + ) + self.assertIn('region', dict(form.fields['available_columns'].widget.choices)) + + def test_form_includes_changelog_message(self): + """The model form must expose the changelog_message meta field.""" + object_type = ObjectType.objects.get_for_model(Site) + form = TableConfigForm(initial={'object_type': object_type.pk, 'table': 'SiteTable'}) + self.assertIn('changelog_message', form.fields) + self.assertIn('changelog_message', form.meta_fields) + + def test_bulk_edit_form_includes_changelog_message(self): + """The bulk edit form must expose the changelog_message meta field.""" + form = TableConfigBulkEditForm() + self.assertIn('changelog_message', form.fields) + self.assertIn('changelog_message', form.meta_fields) diff --git a/netbox/extras/tests/test_models.py b/netbox/extras/tests/test_models.py index d827c641b..a3945f282 100644 --- a/netbox/extras/tests/test_models.py +++ b/netbox/extras/tests/test_models.py @@ -210,6 +210,26 @@ class TableConfigTestCase(TestCase): # Must not raise TypeError: 'NoneType' object is not iterable tc.full_clean() + def test_clean_without_object_type(self): + """full_clean() on an instance missing its object type must raise ValidationError.""" + tc = TableConfig( + table=self.table_name, + name='No object type', + columns=['name'], + ) + with self.assertRaises(ValidationError): + tc.full_clean() + + def test_clean_accepts_columns_none(self): + """full_clean() must report missing columns rather than raise TypeError.""" + tc = TableConfig( + object_type=self.site_ct, + table=self.table_name, + name='No columns', + ) + with self.assertRaises(ValidationError): + tc.full_clean() + class TagTestCase(TestCase): diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 74c88cea8..f97b3581b 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -2,6 +2,7 @@ import uuid from unittest.mock import PropertyMock, patch from django.contrib.contenttypes.models import ContentType +from django.contrib.messages import get_messages from django.test import tag from django.urls import reverse @@ -277,13 +278,16 @@ class SavedFilterTestCase(ViewTestCases.PrimaryObjectViewTestCase): class TableConfigTestCase( ViewTestCases.GetObjectViewTestCase, ViewTestCases.GetObjectChangelogViewTestCase, - ViewTestCases.ListObjectsViewTestCase, + ViewTestCases.CreateObjectViewTestCase, + ViewTestCases.EditObjectViewTestCase, ViewTestCases.DeleteObjectViewTestCase, + ViewTestCases.ListObjectsViewTestCase, + ViewTestCases.BulkEditObjectsViewTestCase, ViewTestCases.BulkDeleteObjectsViewTestCase, ): - # Add/Edit/BulkEdit views require an object_type pre-context from the source - # table view, so they are not exercised here. model = TableConfig + # Selected columns are POSTed as a list but compared as a CSV string + validation_excluded_fields = ('columns',) @classmethod def setUpTestData(cls): @@ -320,6 +324,45 @@ class TableConfigTestCase( ) TableConfig.objects.bulk_create(table_configs) + cls.form_data = { + 'name': 'Table Config X', + 'object_type': site_type.pk, + 'table': 'SiteTable', + 'description': 'A table config', + 'weight': 100, + 'enabled': True, + 'shared': True, + 'columns': ['name', 'status'], + 'ordering': 'name', + } + cls.bulk_edit_data = { + 'weight': 999, + } + + def _get_url(self, action, instance=None): + url = super()._get_url(action, instance) + # The add view requires the table context from the source table view + if action == 'add': + site_type = ObjectType.objects.get_for_model(Site) + url = f'{url}?object_type={site_type.pk}&table=SiteTable' + return url + + def test_add_view_without_table_context(self): + """A GET without the table context params must redirect to the home page.""" + self.add_permissions('extras.add_tableconfig') + response = self.client.get(reverse('extras:tableconfig_add')) + self.assertRedirects(response, reverse('home')) + + messages_list = list(get_messages(response.wsgi_request)) + self.assertEqual(len(messages_list), 1) + self.assertEqual(str(messages_list[0]), 'Table configurations must be created from an object list view.') + + def test_add_view_post_without_table_context(self): + """A POST without the table context must return form errors rather than a server error.""" + self.add_permissions('extras.add_tableconfig') + response = self.client.post(reverse('extras:tableconfig_add'), data={}) + self.assertHttpStatus(response, 200) + class BookmarkTestCase( ViewTestCases.DeleteObjectViewTestCase, diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 344830e67..cec119378 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -481,6 +481,14 @@ class TableConfigEditView(SharedObjectViewMixin, generic.ObjectEditView): form = forms.TableConfigForm template_name = 'extras/tableconfig_edit.html' + def get(self, request, *args, **kwargs): + # The add view requires the object_type & table parameters from the source table view + if not kwargs and not (request.GET.get('object_type') and request.GET.get('table')): + messages.warning(request, _('Table configurations must be created from an object list view.')) + return redirect('home') + + return super().get(request, *args, **kwargs) + def alter_object(self, obj, request, url_args, url_kwargs): if not obj.pk: obj.user = request.user diff --git a/netbox/templates/extras/tableconfig_edit.html b/netbox/templates/extras/tableconfig_edit.html index 31057c298..afd37d72c 100644 --- a/netbox/templates/extras/tableconfig_edit.html +++ b/netbox/templates/extras/tableconfig_edit.html @@ -45,4 +45,9 @@ + + {# Meta fields #} +
+ {% render_field form.changelog_message %} +
{% endblock %} From 22d0b22fc90991bd3a0d684fcaa1eb3719094a69 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 06:29:27 +0000 Subject: [PATCH 27/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 208 ++++++++++--------- 1 file changed, 106 insertions(+), 102 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 767f0f0ea..60201dce0 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-04 06:31+0000\n" +"POT-Creation-Date: 2026-06-05 06:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1186,7 +1186,7 @@ msgid "Term Side" msgstr "" #: netbox/circuits/forms/filtersets.py:296 netbox/dcim/forms/bulk_edit.py:1596 -#: netbox/extras/forms/model_forms.py:775 netbox/extras/ui/panels.py:446 +#: netbox/extras/forms/model_forms.py:794 netbox/extras/ui/panels.py:446 #: netbox/ipam/forms/filtersets.py:154 netbox/ipam/forms/filtersets.py:663 #: netbox/ipam/forms/model_forms.py:353 netbox/ipam/ui/panels.py:122 #: netbox/templates/ipam/vlan_edit.html:42 @@ -1405,7 +1405,7 @@ msgstr "" #: netbox/extras/models/configs.py:286 netbox/extras/models/customfields.py:152 #: netbox/extras/models/models.py:72 netbox/extras/models/models.py:181 #: netbox/extras/models/models.py:428 netbox/extras/models/models.py:496 -#: netbox/extras/models/models.py:578 netbox/extras/models/models.py:707 +#: netbox/extras/models/models.py:578 netbox/extras/models/models.py:711 #: netbox/extras/models/notifications.py:132 netbox/extras/models/tags.py:34 #: netbox/ipam/models/vlans.py:401 netbox/netbox/models/__init__.py:149 #: netbox/netbox/models/__init__.py:188 netbox/netbox/models/__init__.py:238 @@ -1441,7 +1441,7 @@ msgstr "" #: netbox/extras/models/customfields.py:119 netbox/extras/models/models.py:67 #: netbox/extras/models/models.py:176 netbox/extras/models/models.py:318 #: netbox/extras/models/models.py:424 netbox/extras/models/models.py:486 -#: netbox/extras/models/models.py:574 netbox/extras/models/models.py:702 +#: netbox/extras/models/models.py:574 netbox/extras/models/models.py:706 #: netbox/extras/models/notifications.py:127 netbox/extras/models/scripts.py:29 #: netbox/ipam/models/asns.py:18 netbox/ipam/models/fhrp.py:24 #: netbox/ipam/models/services.py:51 netbox/ipam/models/services.py:80 @@ -2080,9 +2080,9 @@ msgstr "" #: netbox/core/forms/filtersets.py:33 netbox/core/forms/model_forms.py:100 #: netbox/core/ui/panels.py:7 netbox/extras/forms/model_forms.py:349 -#: netbox/extras/forms/model_forms.py:682 -#: netbox/extras/forms/model_forms.py:771 -#: netbox/extras/forms/model_forms.py:824 netbox/extras/tables/tables.py:234 +#: netbox/extras/forms/model_forms.py:701 +#: netbox/extras/forms/model_forms.py:790 +#: netbox/extras/forms/model_forms.py:843 netbox/extras/tables/tables.py:234 #: netbox/extras/tables/tables.py:606 netbox/extras/tables/tables.py:636 #: netbox/extras/tables/tables.py:678 #: netbox/templates/core/inc/datafile_panel.html:7 @@ -2179,7 +2179,7 @@ msgid "Before" msgstr "" #: netbox/core/forms/filtersets.py:163 netbox/core/tables/change_logging.py:30 -#: netbox/extras/forms/model_forms.py:559 netbox/extras/ui/panels.py:375 +#: netbox/extras/forms/model_forms.py:578 netbox/extras/ui/panels.py:375 msgid "Action" msgstr "" @@ -2330,7 +2330,7 @@ msgstr "" #: netbox/core/models/config.py:22 netbox/core/models/data.py:290 #: netbox/core/models/files.py:30 netbox/core/models/jobs.py:60 -#: netbox/extras/models/models.py:864 netbox/extras/models/notifications.py:39 +#: netbox/extras/models/models.py:868 netbox/extras/models/notifications.py:39 #: netbox/extras/models/notifications.py:196 #: netbox/netbox/models/features.py:62 netbox/users/models/tokens.py:51 msgid "created" @@ -2824,8 +2824,8 @@ msgstr "" #: netbox/core/ui/panels.py:50 netbox/core/ui/panels.py:79 #: netbox/extras/forms/bulk_import.py:53 netbox/extras/forms/filtersets.py:234 #: netbox/extras/forms/filtersets.py:340 netbox/extras/forms/model_forms.py:407 -#: netbox/extras/forms/model_forms.py:467 -#: netbox/extras/forms/model_forms.py:504 +#: netbox/extras/forms/model_forms.py:486 +#: netbox/extras/forms/model_forms.py:523 #: netbox/tenancy/forms/filtersets.py:121 msgid "Object type" msgstr "" @@ -2859,7 +2859,7 @@ msgstr "" #: netbox/core/views.py:253 netbox/extras/forms/filtersets.py:184 #: netbox/extras/forms/filtersets.py:385 netbox/extras/forms/filtersets.py:408 -#: netbox/extras/forms/filtersets.py:504 netbox/extras/forms/model_forms.py:765 +#: netbox/extras/forms/filtersets.py:504 netbox/extras/forms/model_forms.py:784 #: netbox/extras/ui/panels.py:381 msgid "Data" msgstr "" @@ -3906,14 +3906,14 @@ msgstr "" #: netbox/dcim/filtersets.py:1723 netbox/dcim/filtersets.py:1830 #: netbox/dcim/filtersets.py:2040 netbox/ipam/filtersets.py:659 -#: netbox/ipam/filtersets.py:906 netbox/ipam/filtersets.py:1243 +#: netbox/ipam/filtersets.py:906 netbox/ipam/filtersets.py:1241 #: netbox/virtualization/filtersets.py:205 netbox/vpn/filtersets.py:423 msgid "Device (ID)" msgstr "" #: netbox/dcim/filtersets.py:1730 netbox/dcim/filtersets.py:1837 #: netbox/dcim/filtersets.py:2035 netbox/ipam/filtersets.py:654 -#: netbox/ipam/filtersets.py:901 netbox/ipam/filtersets.py:1238 +#: netbox/ipam/filtersets.py:901 netbox/ipam/filtersets.py:1236 #: netbox/vpn/filtersets.py:418 msgid "Device (name)" msgstr "" @@ -3961,13 +3961,13 @@ msgid "Cable (ID)" msgstr "" #: netbox/dcim/filtersets.py:2045 netbox/ipam/filtersets.py:664 -#: netbox/ipam/filtersets.py:911 netbox/ipam/filtersets.py:1248 +#: netbox/ipam/filtersets.py:911 netbox/ipam/filtersets.py:1246 #: netbox/vpn/filtersets.py:429 msgid "Virtual machine (name)" msgstr "" #: netbox/dcim/filtersets.py:2050 netbox/ipam/filtersets.py:669 -#: netbox/ipam/filtersets.py:916 netbox/ipam/filtersets.py:1253 +#: netbox/ipam/filtersets.py:916 netbox/ipam/filtersets.py:1251 #: netbox/virtualization/filtersets.py:347 #: netbox/virtualization/filtersets.py:405 netbox/vpn/filtersets.py:434 msgid "Virtual machine (ID)" @@ -4066,7 +4066,7 @@ msgstr "" msgid "L2VPN" msgstr "" -#: netbox/dcim/filtersets.py:2182 netbox/ipam/filtersets.py:1182 +#: netbox/dcim/filtersets.py:2182 netbox/ipam/filtersets.py:1180 msgid "VLAN Translation Policy (ID)" msgstr "" @@ -4189,9 +4189,9 @@ msgid "Power panel (ID)" msgstr "" #: netbox/dcim/forms/bulk_create.py:42 netbox/extras/forms/filtersets.py:496 -#: netbox/extras/forms/model_forms.py:675 -#: netbox/extras/forms/model_forms.py:760 -#: netbox/extras/forms/model_forms.py:812 netbox/extras/ui/panels.py:103 +#: netbox/extras/forms/model_forms.py:694 +#: netbox/extras/forms/model_forms.py:779 +#: netbox/extras/forms/model_forms.py:831 netbox/extras/ui/panels.py:103 #: netbox/netbox/forms/bulk_import.py:27 netbox/netbox/forms/mixins.py:135 #: netbox/netbox/tables/columns.py:500 #: netbox/templates/circuits/inc/circuit_termination.html:29 @@ -4459,7 +4459,7 @@ msgid "Device Type" msgstr "" #: netbox/dcim/forms/bulk_edit.py:566 netbox/dcim/forms/model_forms.py:457 -#: netbox/dcim/views.py:1684 netbox/extras/forms/model_forms.py:670 +#: netbox/dcim/views.py:1684 netbox/extras/forms/model_forms.py:689 msgid "Schema" msgstr "" @@ -4469,7 +4469,7 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:1146 netbox/dcim/forms/filtersets.py:1278 #: netbox/dcim/forms/model_forms.py:463 netbox/dcim/forms/model_forms.py:476 #: netbox/dcim/tables/modules.py:43 netbox/dcim/tables/modules.py:97 -#: netbox/extras/forms/filtersets.py:418 netbox/extras/forms/model_forms.py:695 +#: netbox/extras/forms/filtersets.py:418 netbox/extras/forms/model_forms.py:714 #: netbox/extras/tables/tables.py:633 netbox/templates/account/base.html:7 #: netbox/templates/dcim/panels/module_type.html:15 #: netbox/templates/inc/user_menu.html:38 netbox/vpn/forms/bulk_edit.py:213 @@ -5531,7 +5531,7 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:1665 netbox/extras/forms/bulk_edit.py:433 #: netbox/extras/forms/bulk_import.py:332 netbox/extras/forms/filtersets.py:595 -#: netbox/extras/forms/model_forms.py:877 netbox/extras/tables/tables.py:765 +#: netbox/extras/forms/model_forms.py:896 netbox/extras/tables/tables.py:765 msgid "Kind" msgstr "" @@ -5707,7 +5707,7 @@ msgid "" "hyphen." msgstr "" -#: netbox/dcim/forms/model_forms.py:459 netbox/extras/forms/model_forms.py:672 +#: netbox/dcim/forms/model_forms.py:459 netbox/extras/forms/model_forms.py:691 msgid "Enter a valid JSON schema to define supported attributes." msgstr "" @@ -7084,7 +7084,7 @@ msgid "Numeric identifier unique to the parent device" msgstr "" #: netbox/dcim/models/devices.py:1295 netbox/extras/models/customfields.py:263 -#: netbox/extras/models/models.py:118 netbox/extras/models/models.py:824 +#: netbox/extras/models/models.py:118 netbox/extras/models/models.py:828 #: netbox/netbox/models/__init__.py:154 netbox/netbox/models/__init__.py:193 #: netbox/netbox/models/__init__.py:243 msgid "comments" @@ -7619,7 +7619,7 @@ msgid "VMs" msgstr "" #: netbox/dcim/tables/devices.py:105 netbox/dcim/tables/devices.py:225 -#: netbox/extras/forms/model_forms.py:823 netbox/extras/ui/panels.py:460 +#: netbox/extras/forms/model_forms.py:842 netbox/extras/ui/panels.py:460 #: netbox/templates/extras/object_render_config.html:12 #: netbox/templates/extras/object_render_config.html:15 #: netbox/virtualization/tables/virtualmachines.py:114 @@ -7869,7 +7869,7 @@ msgid "Module Types" msgstr "" #: netbox/dcim/tables/devicetypes.py:58 netbox/extras/forms/filtersets.py:466 -#: netbox/extras/forms/model_forms.py:730 netbox/extras/tables/tables.py:725 +#: netbox/extras/forms/model_forms.py:749 netbox/extras/tables/tables.py:725 #: netbox/netbox/navigation/menu.py:87 msgid "Platforms" msgstr "" @@ -8030,7 +8030,7 @@ msgid "Total U's" msgstr "" #: netbox/dcim/tables/sites.py:22 netbox/dcim/tables/sites.py:41 -#: netbox/extras/forms/filtersets.py:446 netbox/extras/forms/model_forms.py:710 +#: netbox/extras/forms/filtersets.py:446 netbox/extras/forms/model_forms.py:729 #: netbox/ipam/forms/bulk_edit.py:115 netbox/ipam/forms/model_forms.py:164 #: netbox/ipam/tables/asn.py:80 netbox/netbox/navigation/menu.py:18 #: netbox/netbox/navigation/menu.py:22 @@ -8041,7 +8041,7 @@ msgstr "" msgid "VLAN Groups" msgstr "" -#: netbox/dcim/tests/test_api.py:59 +#: netbox/dcim/tests/test_api.py:61 msgid "Test case must set peer_termination_type" msgstr "" @@ -8172,7 +8172,7 @@ msgid "Application Services" msgstr "" #: netbox/dcim/views.py:2858 netbox/extras/forms/filtersets.py:407 -#: netbox/extras/forms/model_forms.py:770 netbox/extras/ui/panels.py:435 +#: netbox/extras/forms/model_forms.py:789 netbox/extras/ui/panels.py:435 #: netbox/virtualization/forms/model_forms.py:270 #: netbox/virtualization/views.py:569 msgid "Config Context" @@ -8269,7 +8269,7 @@ msgstr "" msgid "An error occurred while rendering the config template." msgstr "" -#: netbox/extras/api/mixins.py:117 netbox/extras/views.py:1298 +#: netbox/extras/api/mixins.py:117 netbox/extras/views.py:1306 #, python-brace-format msgid "Config template with ID {id} not found." msgstr "" @@ -8481,12 +8481,12 @@ msgstr "" msgid "Failure" msgstr "" -#: netbox/extras/choices.py:282 netbox/extras/forms/model_forms.py:516 -#: netbox/extras/forms/model_forms.py:593 netbox/extras/ui/panels.py:329 +#: netbox/extras/choices.py:282 netbox/extras/forms/model_forms.py:535 +#: netbox/extras/forms/model_forms.py:612 netbox/extras/ui/panels.py:329 msgid "Webhook" msgstr "" -#: netbox/extras/choices.py:283 netbox/extras/forms/model_forms.py:581 +#: netbox/extras/choices.py:283 netbox/extras/forms/model_forms.py:600 #: netbox/templates/extras/script/base.html:29 msgid "Script" msgstr "" @@ -8680,7 +8680,7 @@ msgstr "" msgid "Tenant group (slug)" msgstr "" -#: netbox/extras/filtersets.py:830 netbox/extras/forms/model_forms.py:658 +#: netbox/extras/filtersets.py:830 netbox/extras/forms/model_forms.py:677 #: netbox/extras/ui/panels.py:391 msgid "Tag" msgstr "" @@ -8808,7 +8808,7 @@ msgid "CA file path" msgstr "" #: netbox/extras/forms/bulk_edit.py:296 netbox/extras/forms/bulk_import.py:269 -#: netbox/extras/forms/model_forms.py:540 +#: netbox/extras/forms/model_forms.py:559 msgid "Event types" msgstr "" @@ -8831,8 +8831,8 @@ msgstr "" #: netbox/extras/forms/model_forms.py:306 #: netbox/extras/forms/model_forms.py:338 #: netbox/extras/forms/model_forms.py:381 -#: netbox/extras/forms/model_forms.py:535 -#: netbox/extras/forms/model_forms.py:652 netbox/users/forms/model_forms.py:335 +#: netbox/extras/forms/model_forms.py:554 +#: netbox/extras/forms/model_forms.py:671 netbox/users/forms/model_forms.py:335 msgid "Object types" msgstr "" @@ -8930,7 +8930,7 @@ msgstr "" #: netbox/extras/forms/bulk_import.py:233 #: netbox/extras/forms/model_forms.py:373 -#: netbox/extras/forms/model_forms.py:852 +#: netbox/extras/forms/model_forms.py:871 msgid "Must specify either local content or a data file" msgstr "" @@ -8975,7 +8975,7 @@ msgid "Comments" msgstr "" #: netbox/extras/forms/bulk_import.py:350 -#: netbox/extras/forms/model_forms.py:483 netbox/extras/ui/panels.py:321 +#: netbox/extras/forms/model_forms.py:502 netbox/extras/ui/panels.py:321 #: netbox/netbox/navigation/menu.py:423 netbox/users/forms/filtersets.py:181 #: netbox/users/forms/model_forms.py:274 netbox/users/forms/model_forms.py:286 #: netbox/users/forms/model_forms.py:361 netbox/users/forms/model_forms.py:557 @@ -8989,7 +8989,7 @@ msgid "User names separated by commas, encased with double quotes" msgstr "" #: netbox/extras/forms/bulk_import.py:357 -#: netbox/extras/forms/model_forms.py:478 netbox/extras/ui/panels.py:316 +#: netbox/extras/forms/model_forms.py:497 netbox/extras/ui/panels.py:316 #: netbox/netbox/navigation/menu.py:306 netbox/netbox/navigation/menu.py:424 #: netbox/tenancy/forms/bulk_edit.py:121 netbox/tenancy/forms/filtersets.py:107 #: netbox/tenancy/forms/model_forms.py:93 netbox/tenancy/tables/contacts.py:57 @@ -9025,7 +9025,7 @@ msgstr "" #: netbox/extras/forms/filtersets.py:185 netbox/extras/forms/filtersets.py:505 #: netbox/extras/forms/model_forms.py:351 -#: netbox/extras/forms/model_forms.py:827 +#: netbox/extras/forms/model_forms.py:846 msgid "Rendering" msgstr "" @@ -9054,44 +9054,44 @@ msgstr "" msgid "Allowed object type" msgstr "" -#: netbox/extras/forms/filtersets.py:436 netbox/extras/forms/model_forms.py:700 +#: netbox/extras/forms/filtersets.py:436 netbox/extras/forms/model_forms.py:719 #: netbox/netbox/navigation/menu.py:20 msgid "Regions" msgstr "" -#: netbox/extras/forms/filtersets.py:441 netbox/extras/forms/model_forms.py:705 +#: netbox/extras/forms/filtersets.py:441 netbox/extras/forms/model_forms.py:724 msgid "Site groups" msgstr "" -#: netbox/extras/forms/filtersets.py:451 netbox/extras/forms/model_forms.py:715 +#: netbox/extras/forms/filtersets.py:451 netbox/extras/forms/model_forms.py:734 #: netbox/netbox/navigation/menu.py:23 msgid "Locations" msgstr "" -#: netbox/extras/forms/filtersets.py:456 netbox/extras/forms/model_forms.py:720 +#: netbox/extras/forms/filtersets.py:456 netbox/extras/forms/model_forms.py:739 msgid "Device types" msgstr "" -#: netbox/extras/forms/filtersets.py:461 netbox/extras/forms/model_forms.py:725 +#: netbox/extras/forms/filtersets.py:461 netbox/extras/forms/model_forms.py:744 msgid "Roles" msgstr "" -#: netbox/extras/forms/filtersets.py:471 netbox/extras/forms/model_forms.py:735 +#: netbox/extras/forms/filtersets.py:471 netbox/extras/forms/model_forms.py:754 msgid "Cluster types" msgstr "" -#: netbox/extras/forms/filtersets.py:476 netbox/extras/forms/model_forms.py:740 +#: netbox/extras/forms/filtersets.py:476 netbox/extras/forms/model_forms.py:759 msgid "Cluster groups" msgstr "" -#: netbox/extras/forms/filtersets.py:481 netbox/extras/forms/model_forms.py:745 +#: netbox/extras/forms/filtersets.py:481 netbox/extras/forms/model_forms.py:764 #: netbox/netbox/navigation/menu.py:275 netbox/netbox/navigation/menu.py:277 #: netbox/virtualization/tables/clusters.py:23 #: netbox/virtualization/tables/clusters.py:46 msgid "Clusters" msgstr "" -#: netbox/extras/forms/filtersets.py:486 netbox/extras/forms/model_forms.py:750 +#: netbox/extras/forms/filtersets.py:486 netbox/extras/forms/model_forms.py:769 msgid "Tenant groups" msgstr "" @@ -9183,7 +9183,7 @@ msgid "" msgstr "" #: netbox/extras/forms/model_forms.py:342 -#: netbox/extras/forms/model_forms.py:817 +#: netbox/extras/forms/model_forms.py:836 msgid "Template code" msgstr "" @@ -9192,7 +9192,7 @@ msgid "Export Template" msgstr "" #: netbox/extras/forms/model_forms.py:366 -#: netbox/extras/forms/model_forms.py:845 +#: netbox/extras/forms/model_forms.py:864 msgid "Template content is populated from the remote source selected below." msgstr "" @@ -9220,58 +9220,58 @@ msgstr "" msgid "Selected Columns" msgstr "" -#: netbox/extras/forms/model_forms.py:497 +#: netbox/extras/forms/model_forms.py:516 msgid "A notification group specify at least one user or group." msgstr "" -#: netbox/extras/forms/model_forms.py:519 netbox/extras/ui/panels.py:336 +#: netbox/extras/forms/model_forms.py:538 netbox/extras/ui/panels.py:336 msgid "HTTP Request" msgstr "" -#: netbox/extras/forms/model_forms.py:521 netbox/extras/ui/panels.py:345 +#: netbox/extras/forms/model_forms.py:540 netbox/extras/ui/panels.py:345 msgid "SSL" msgstr "" -#: netbox/extras/forms/model_forms.py:543 +#: netbox/extras/forms/model_forms.py:562 msgid "Action choice" msgstr "" -#: netbox/extras/forms/model_forms.py:548 +#: netbox/extras/forms/model_forms.py:567 msgid "Enter conditions in JSON format." msgstr "" -#: netbox/extras/forms/model_forms.py:552 +#: netbox/extras/forms/model_forms.py:571 msgid "" "Enter parameters to pass to the action in JSON format." msgstr "" -#: netbox/extras/forms/model_forms.py:557 netbox/extras/ui/panels.py:356 +#: netbox/extras/forms/model_forms.py:576 netbox/extras/ui/panels.py:356 msgid "Event Rule" msgstr "" -#: netbox/extras/forms/model_forms.py:558 +#: netbox/extras/forms/model_forms.py:577 msgid "Triggers" msgstr "" -#: netbox/extras/forms/model_forms.py:605 +#: netbox/extras/forms/model_forms.py:624 msgid "Notification group" msgstr "" -#: netbox/extras/forms/model_forms.py:681 netbox/extras/ui/panels.py:424 +#: netbox/extras/forms/model_forms.py:700 netbox/extras/ui/panels.py:424 msgid "Config Context Profile" msgstr "" -#: netbox/extras/forms/model_forms.py:755 netbox/netbox/navigation/menu.py:29 +#: netbox/extras/forms/model_forms.py:774 netbox/netbox/navigation/menu.py:29 #: netbox/tenancy/tables/tenants.py:18 msgid "Tenants" msgstr "" -#: netbox/extras/forms/model_forms.py:799 +#: netbox/extras/forms/model_forms.py:818 msgid "Data is populated from the remote source selected below." msgstr "" -#: netbox/extras/forms/model_forms.py:805 +#: netbox/extras/forms/model_forms.py:824 msgid "Must specify either local data or a data file" msgstr "" @@ -10044,63 +10044,63 @@ msgstr "" msgid "table configs" msgstr "" -#: netbox/extras/models/models.py:655 +#: netbox/extras/models/models.py:659 #, python-brace-format msgid "Unknown table: {name}" msgstr "" -#: netbox/extras/models/models.py:666 netbox/extras/models/models.py:673 +#: netbox/extras/models/models.py:670 netbox/extras/models/models.py:677 #, python-brace-format msgid "Unknown column: {name}" msgstr "" -#: netbox/extras/models/models.py:696 +#: netbox/extras/models/models.py:700 msgid "image height" msgstr "" -#: netbox/extras/models/models.py:699 +#: netbox/extras/models/models.py:703 msgid "image width" msgstr "" -#: netbox/extras/models/models.py:722 +#: netbox/extras/models/models.py:726 msgid "image attachment" msgstr "" -#: netbox/extras/models/models.py:723 +#: netbox/extras/models/models.py:727 msgid "image attachments" msgstr "" -#: netbox/extras/models/models.py:737 +#: netbox/extras/models/models.py:741 #, python-brace-format msgid "Image attachments cannot be assigned to this object type ({type})." msgstr "" -#: netbox/extras/models/models.py:818 +#: netbox/extras/models/models.py:822 msgid "kind" msgstr "" -#: netbox/extras/models/models.py:833 +#: netbox/extras/models/models.py:837 msgid "journal entry" msgstr "" -#: netbox/extras/models/models.py:834 +#: netbox/extras/models/models.py:838 msgid "journal entries" msgstr "" -#: netbox/extras/models/models.py:852 +#: netbox/extras/models/models.py:856 #, python-brace-format msgid "Journaling is not supported for this object type ({type})." msgstr "" -#: netbox/extras/models/models.py:895 +#: netbox/extras/models/models.py:899 msgid "bookmark" msgstr "" -#: netbox/extras/models/models.py:896 +#: netbox/extras/models/models.py:900 msgid "bookmarks" msgstr "" -#: netbox/extras/models/models.py:912 +#: netbox/extras/models/models.py:916 #, python-brace-format msgid "Bookmarks cannot be assigned to this object type ({type})." msgstr "" @@ -10506,55 +10506,59 @@ msgstr "" msgid "Link URL" msgstr "" -#: netbox/extras/views.py:320 netbox/extras/views.py:1202 +#: netbox/extras/views.py:320 netbox/extras/views.py:1210 msgid "Environment Parameters" msgstr "" -#: netbox/extras/views.py:323 netbox/extras/views.py:1205 +#: netbox/extras/views.py:323 netbox/extras/views.py:1213 msgid "Template" msgstr "" -#: netbox/extras/views.py:764 +#: netbox/extras/views.py:487 +msgid "Table configurations must be created from an object list view." +msgstr "" + +#: netbox/extras/views.py:772 msgid "Additional Headers" msgstr "" -#: netbox/extras/views.py:765 +#: netbox/extras/views.py:773 msgid "Body Template" msgstr "" -#: netbox/extras/views.py:834 +#: netbox/extras/views.py:842 msgid "Conditions" msgstr "" -#: netbox/extras/views.py:908 +#: netbox/extras/views.py:916 msgid "Tagged Objects" msgstr "" -#: netbox/extras/views.py:1000 +#: netbox/extras/views.py:1008 msgid "JSON Schema" msgstr "" -#: netbox/extras/views.py:1493 +#: netbox/extras/views.py:1501 msgid "Your dashboard has been reset." msgstr "" -#: netbox/extras/views.py:1539 +#: netbox/extras/views.py:1547 msgid "Added widget: " msgstr "" -#: netbox/extras/views.py:1580 +#: netbox/extras/views.py:1588 msgid "Updated widget: " msgstr "" -#: netbox/extras/views.py:1616 +#: netbox/extras/views.py:1624 msgid "Deleted widget: " msgstr "" -#: netbox/extras/views.py:1618 +#: netbox/extras/views.py:1626 msgid "Error deleting widget: " msgstr "" -#: netbox/extras/views.py:1733 +#: netbox/extras/views.py:1741 msgid "Unable to run script: RQ worker process not running." msgstr "" @@ -10771,39 +10775,39 @@ msgstr "" msgid "Assigned VM interface" msgstr "" -#: netbox/ipam/filtersets.py:1189 +#: netbox/ipam/filtersets.py:1187 msgid "VLAN Translation Policy (name)" msgstr "" -#: netbox/ipam/filtersets.py:1258 +#: netbox/ipam/filtersets.py:1256 msgid "FHRP Group (name)" msgstr "" -#: netbox/ipam/filtersets.py:1263 +#: netbox/ipam/filtersets.py:1261 msgid "FHRP Group (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1268 +#: netbox/ipam/filtersets.py:1266 msgid "IP address (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1274 netbox/ipam/models/ip.py:851 +#: netbox/ipam/filtersets.py:1272 netbox/ipam/models/ip.py:851 msgid "IP address" msgstr "" -#: netbox/ipam/filtersets.py:1327 +#: netbox/ipam/filtersets.py:1325 msgid "Primary IPv4 (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1334 +#: netbox/ipam/filtersets.py:1332 msgid "Primary IPv4 (address)" msgstr "" -#: netbox/ipam/filtersets.py:1340 +#: netbox/ipam/filtersets.py:1338 msgid "Primary IPv6 (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1347 +#: netbox/ipam/filtersets.py:1345 msgid "Primary IPv6 (address)" msgstr "" From 87c53aaaeb7f1cfe538c75241d4a5179a7ca81df Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Jun 2026 10:05:39 -0400 Subject: [PATCH 28/58] Fixes #22399: Enforce object permissions for relevant static media (#22400) --- netbox/netbox/tests/test_views.py | 69 ++++++++++++++++++++++++++++++- netbox/netbox/views/misc.py | 31 +++++++++++++- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/tests/test_views.py b/netbox/netbox/tests/test_views.py index ee9599dc2..96c31ba16 100644 --- a/netbox/netbox/tests/test_views.py +++ b/netbox/netbox/tests/test_views.py @@ -1,9 +1,13 @@ import urllib.parse +from unittest.mock import patch +from django.contrib.contenttypes.models import ContentType +from django.http import HttpResponse from django.test import Client, override_settings from django.urls import reverse -from dcim.models import Site +from dcim.models import DeviceType, Manufacturer, Site +from extras.models import ImageAttachment from netbox.constants import EMPTY_TABLE_TEXT from netbox.search.backends import search_backend from utilities.testing import TestCase @@ -78,6 +82,27 @@ class SearchViewTestCase(TestCase): class MediaViewTestCase(TestCase): + @classmethod + def setUpTestData(cls): + site = Site.objects.create(name='Site 1', slug='site-1') + ct = ContentType.objects.get_for_model(Site) + cls.image_attachment = ImageAttachment.objects.create( + object_type=ct, + object_id=site.pk, + name='Test Image', + image='image-attachments/site_1_test.jpg', + image_height=100, + image_width=100, + ) + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + cls.device_type = DeviceType.objects.create( + model='Device Type 1', + slug='device-type-1', + manufacturer=manufacturer, + front_image='devicetype-images/front.jpg', + ) + def test_media_login_required(self): url = reverse('media', kwargs={'path': 'foo.txt'}) response = Client().get(url) @@ -92,3 +117,45 @@ class MediaViewTestCase(TestCase): # Unauthenticated request should return a 404 (not found) self.assertHttpStatus(response, 404) + + def test_image_attachment_with_permission(self): + self.add_permissions('extras.view_imageattachment') + url = reverse('media', kwargs={'path': self.image_attachment.image.name}) + with patch('netbox.views.misc.serve', return_value=HttpResponse(status=200)): + response = self.client.get(url) + self.assertHttpStatus(response, 200) + self.assertEqual(response['Content-Disposition'], 'attachment') + self.assertEqual(response['X-Content-Type-Options'], 'nosniff') + + def test_image_attachment_without_permission(self): + url = reverse('media', kwargs={'path': self.image_attachment.image.name}) + response = self.client.get(url) + self.assertHttpStatus(response, 404) + + def test_image_attachment_traversal_without_permission(self): + # A traversal path that normalizes to a protected directory must still be denied. + traversal_path = 'foo/../' + self.image_attachment.image.name + url = reverse('media', kwargs={'path': traversal_path}) + response = self.client.get(url) + self.assertHttpStatus(response, 404) + + def test_device_type_with_permission(self): + self.add_permissions('dcim.view_devicetype') + url = reverse('media', kwargs={'path': self.device_type.front_image.name}) + with patch('netbox.views.misc.serve', return_value=HttpResponse(status=200)): + response = self.client.get(url) + self.assertHttpStatus(response, 200) + self.assertEqual(response['Content-Disposition'], 'attachment') + self.assertEqual(response['X-Content-Type-Options'], 'nosniff') + + def test_device_type_without_permission(self): + url = reverse('media', kwargs={'path': self.device_type.front_image.name}) + response = self.client.get(url) + self.assertHttpStatus(response, 404) + + def test_device_type_traversal_without_permission(self): + # A traversal path that normalizes to a protected directory must still be denied. + traversal_path = 'foo/../' + self.device_type.front_image.name + url = reverse('media', kwargs={'path': traversal_path}) + response = self.client.get(url) + self.assertHttpStatus(response, 404) diff --git a/netbox/netbox/views/misc.py b/netbox/netbox/views/misc.py index 29833fba9..2eeba7cb8 100644 --- a/netbox/netbox/views/misc.py +++ b/netbox/netbox/views/misc.py @@ -1,4 +1,5 @@ import logging +import posixpath import re from collections import namedtuple @@ -6,6 +7,8 @@ from django.conf import settings from django.contrib import messages from django.contrib.contenttypes.models import ContentType from django.core.cache import cache +from django.db.models import Q +from django.http import Http404 from django.shortcuts import redirect, render from django.utils.translation import gettext_lazy as _ from django.views.generic import View @@ -13,8 +16,10 @@ from django.views.static import serve from django_tables2 import RequestConfig from packaging import version +from dcim.models import DeviceType from extras.constants import DEFAULT_DASHBOARD from extras.dashboard.utils import get_dashboard, get_default_dashboard +from extras.models import ImageAttachment from netbox.forms import SearchForm from netbox.search import LookupTypes from netbox.search.backends import search_backend @@ -131,7 +136,29 @@ class SearchView(ConditionalLoginRequiredMixin, View): class MediaView(TokenConditionalLoginRequiredMixin, View): """ - Wrap Django's serve() view to enforce LOGIN_REQUIRED for static media. + Serve uploaded media files, enforcing authentication and view permission on the associated object. """ def get(self, request, path): - return serve(request, path, document_root=settings.MEDIA_ROOT) + + # Normalize the path to prevent traversal sequences (e.g. "foo/../image-attachments/...") + # from bypassing the directory checks below. + path = posixpath.normpath(path).lstrip('/') + + # For known upload directories, resolve the path to an owning record and + # enforce object-level view permission. restrict() returns .none() when the + # user lacks permission, so a denial and a missing file are both 404s. + # Paths outside these directories (e.g. plugin uploads) fall through + # to the original behaviour. + if path.startswith('image-attachments/'): + if not ImageAttachment.objects.restrict(request.user, 'view').filter(image=path).exists(): + raise Http404 + elif path.startswith('devicetype-images/'): + if not DeviceType.objects.restrict(request.user, 'view').filter( + Q(front_image=path) | Q(rear_image=path) + ).exists(): + raise Http404 + + response = serve(request, path, document_root=settings.MEDIA_ROOT) + response['Content-Disposition'] = 'attachment' + response['X-Content-Type-Options'] = 'nosniff' + return response From 70391e5a0b8577e8cdb1099630a14cddc6f575f0 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Jun 2026 12:28:31 -0400 Subject: [PATCH 29/58] Closes #22392: Deprecate support for Redis 5.x (#22405) --- docs/installation/2-redis.md | 5 ++++- docs/installation/index.md | 3 ++- netbox/core/apps.py | 2 +- netbox/core/checks.py | 26 ++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/docs/installation/2-redis.md b/docs/installation/2-redis.md index 00a02b889..f412cced1 100644 --- a/docs/installation/2-redis.md +++ b/docs/installation/2-redis.md @@ -8,7 +8,10 @@ sudo apt install -y redis-server ``` -Before continuing, verify that your installed version of Redis is at least v5.0: +Before continuing, verify that your installed version of Redis is at least v6.0: + +!!! warning "Redis v5.x is deprecated" + Support for Redis versions older than 6.0 is deprecated and will be removed in NetBox v4.7. ```no-highlight redis-server -v diff --git a/docs/installation/index.md b/docs/installation/index.md index a20bcd369..38f5b1d76 100644 --- a/docs/installation/index.md +++ b/docs/installation/index.md @@ -29,9 +29,10 @@ The following sections detail how to set up a new instance of NetBox: |------------|--------------------| | Python | 3.12, 3.13, 3.14 | | PostgreSQL | 14+ [^1] | -| Redis | 5.0+ | +| Redis | 5.0+ [^2] | [^1]: Support for PostgreSQL 14 is deprecated and will be removed in NetBox v4.7. PostgreSQL 15 or later will be required. +[^2]: Support for Redis versions older than 6.0 is deprecated and will be removed in NetBox v4.7. Redis 6.0 or later will be required. Below is a simplified overview of the NetBox application stack for reference: diff --git a/netbox/core/apps.py b/netbox/core/apps.py index 86ed763b5..27f457607 100644 --- a/netbox/core/apps.py +++ b/netbox/core/apps.py @@ -22,7 +22,7 @@ class CoreConfig(AppConfig): def ready(self): from core.api import schema # noqa: F401 - from core.checks import check_duplicate_indexes, check_postgresql_version # noqa: F401 + from core.checks import check_duplicate_indexes, check_postgresql_version, check_redis_version # noqa: F401 from netbox import context_managers # noqa: F401 from netbox.models.features import register_models diff --git a/netbox/core/checks.py b/netbox/core/checks.py index 90300bdd3..d452641c7 100644 --- a/netbox/core/checks.py +++ b/netbox/core/checks.py @@ -1,4 +1,5 @@ from django.apps import apps +from django.core.cache import cache from django.core.checks import Error, Tags, Warning, register from django.db import connection from django.db.models import Index, UniqueConstraint @@ -6,6 +7,7 @@ from django.db.models import Index, UniqueConstraint __all__ = ( 'check_duplicate_indexes', 'check_postgresql_version', + 'check_redis_version', ) @@ -67,3 +69,27 @@ def check_postgresql_version(app_configs, **kwargs): except Exception: pass return warnings + + +@register(Tags.caches) +def check_redis_version(app_configs, **kwargs): + """ + Warn if the Redis version is less than 6.0, as support for Redis older than 6.0 + will be removed in NetBox v4.7. + """ + warnings = [] + try: + client = cache.client.get_client() + redis_version = tuple(int(x) for x in client.info()['redis_version'].split('.')) + if redis_version < (6, 0): + warnings.append( + Warning( + f'Support for Redis {".".join(str(x) for x in redis_version)} is deprecated and will be ' + f'removed in NetBox v4.7.', + hint='Please upgrade to Redis 6.0 or later.', + id='netbox.W002', + ) + ) + except Exception: + pass + return warnings From c81bd39f7dd82610d327f994eb350066e737e71d Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 06:21:09 +0000 Subject: [PATCH 30/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 60201dce0..5afc5724c 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-05 06:29+0000\n" +"POT-Creation-Date: 2026-06-09 06:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -13415,7 +13415,7 @@ msgstr "" msgid "{class_name} must implement get_children()" msgstr "" -#: netbox/netbox/views/misc.py:49 +#: netbox/netbox/views/misc.py:54 msgid "" "There was an error loading the dashboard configuration. A default dashboard " "is in use." From f732a8e878889974ed4f86e90c408e9b8c1afdde Mon Sep 17 00:00:00 2001 From: mburggraf Date: Tue, 9 Jun 2026 19:55:20 +0200 Subject: [PATCH 31/58] Fixes #22376: Remove files from request for script action event rules --- netbox/extras/events.py | 2 +- netbox/extras/tests/test_event_rules.py | 153 +++++++++++++++++++++++- netbox/utilities/request.py | 3 + 3 files changed, 154 insertions(+), 4 deletions(-) diff --git a/netbox/extras/events.py b/netbox/extras/events.py index a2f3aac78..c01e02ed5 100644 --- a/netbox/extras/events.py +++ b/netbox/extras/events.py @@ -257,7 +257,7 @@ def process_event_rules(event_rules, object_type, event): if 'snapshots' in event: params['snapshots'] = event['snapshots'] if 'request' in event: - params['request'] = copy_safe_request(event['request']) + params['request'] = copy_safe_request(event['request'], include_files=False) # Enqueue the job ScriptJob.enqueue(**params) diff --git a/netbox/extras/tests/test_event_rules.py b/netbox/extras/tests/test_event_rules.py index 42faf5bdc..f2d753be5 100644 --- a/netbox/extras/tests/test_event_rules.py +++ b/netbox/extras/tests/test_event_rules.py @@ -1,5 +1,7 @@ import json +import logging import uuid +from io import BytesIO from unittest import skipIf from unittest.mock import Mock, patch @@ -8,16 +10,19 @@ from django.conf import settings from django.http import HttpResponse from django.test import RequestFactory, tag from django.urls import reverse +from PIL import Image from requests import Session from rest_framework import status +from core.choices import ManagedFileRootPathChoices from core.events import * -from core.models import ObjectType +from core.models import Job, ObjectType from dcim.choices import SiteStatusChoices -from dcim.models import Interface, Site +from dcim.models import DeviceType, Interface, Manufacturer, Site from extras.choices import EventRuleActionChoices from extras.events import enqueue_event, flush_events, serialize_for_event -from extras.models import EventRule, Script, Tag, Webhook +from extras.models import EventRule, Script, ScriptModule, Tag, Webhook +from extras.scripts import Script as ScriptBase from extras.signals import process_job_end_event_rules from extras.webhooks import generate_signature, send_webhook from netbox.context_managers import event_tracking @@ -653,3 +658,145 @@ class EventRuleTestCase(APITestCase): self.add_permissions('dcim.add_site') response = self.client.post(url, {'name': 'Site X', 'slug': 'site-x'}, format='json', **self.header) self.assertHttpStatus(response, status.HTTP_201_CREATED) + + @tag('regression') + def test_eventrule_script_action_with_object_image_files(self): + """ + Verify that a Script event-rule action can be enqueued and executed cleanly when the + triggering object carries uploaded files (e.g. DeviceType images). + This is a regression test for issue #22376. + + """ + # Create a dummy script class and an instance of it + class DummyScript(ScriptBase): + class Meta: + name = "Dummy Script" + + def run(self, data, commit=True): + return "finished successfully" + + dummy_script = DummyScript() + + # Create ScriptModule and Script + with patch.object(ScriptModule, 'sync_classes'): + module = ScriptModule.objects.create( + file_root=ManagedFileRootPathChoices.SCRIPTS, + file_path='dummy_script.py', + ) + script = Script.objects.create( + module=module, + name='Dummy Script', + is_executable=True, + ) + script_type = ObjectType.objects.get_for_model(Script) + + # Create an event rule that triggers on DeviceType update with Script action + devicetype_type = ObjectType.objects.get_for_model(DeviceType) + event_rule = EventRule.objects.create( + name='Test Script Event Rule with Files', + event_types=[OBJECT_UPDATED], + action_type=EventRuleActionChoices.SCRIPT, + action_object_type=script_type, + action_object_id=script.pk, + ) + event_rule.object_types.set([devicetype_type]) + + # Create a manufacturer and DeviceType + manufacturer = Manufacturer.objects.create( + name='Test Manufacturer', + slug='test-manufacturer', + ) + devicetype = DeviceType.objects.create( + model='Test DeviceType', + slug="test-devicetype", + manufacturer=manufacturer, + ) + + # Create an image file + image = BytesIO() + Image.new('RGB', (1, 1)).save(image, format='PNG') + image.name = 'test_image.png' + image.seek(0) + + # PATCH the DeviceType via REST API to add the image + data = { + 'front_image': image, + } + url = reverse('dcim-api:devicetype-detail', kwargs={'pk': devicetype.pk}) + self.add_permissions('dcim.change_devicetype') + + # Mock the script's python_class to prevent the test from trying to load from disk + with patch.object(Script, 'python_class') as mock: + mock.return_value = dummy_script + # Since in core/models/jobs.py Jobs are enqueued with a transaction.on_commit-handler + # we simulate commit by using captureOnCommitCallbacks context manager + with self.captureOnCommitCallbacks(execute=True): + response = self.client.patch(url, data, format='multipart', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + + # Assert that the script job was enqueued cleanly and is waiting for execution + self.assertEqual(self.queue.count, 1) + script_job = Job.objects.filter(name=dummy_script.name).last() + self.assertEqual(script_job.status, "pending") + + # silence rqworker (cleaner output) and trigger job execution + logging.getLogger('rq.worker').setLevel(logging.ERROR) + django_rq.get_worker().work(burst=True) + + # Assert that our script was executed without any errors + script_job.refresh_from_db() + self.assertEqual(script_job.status, "completed") + self.assertEqual(script_job.data.get('output', ''), "finished successfully") + + @tag('regression') + def test_eventrule_webhook_action_with_object_image_files(self): + """ + Verify that a Webhook event-rule action can be enqueued and executed cleanly when + the triggering object carries uploaded files (e.g. DeviceType images). + This is a regression test for issue #20873. + """ + # Create an event rule that triggers on DeviceType update with Script action + webhook = Webhook.objects.get(name='Webhook 1') + webhook_type = ObjectType.objects.get_for_model(Webhook) + devicetype_type = ObjectType.objects.get_for_model(DeviceType) + event_rule = EventRule.objects.create( + name='Test Webhook Event Rule with Files', + event_types=[OBJECT_UPDATED], + action_type=EventRuleActionChoices.WEBHOOK, + action_object_type=webhook_type, + action_object_id=webhook.pk, + ) + event_rule.object_types.set([devicetype_type]) + + # Create a manufacturer and DeviceType + manufacturer = Manufacturer.objects.create( + name='Test Manufacturer', + slug='test-manufacturer', + ) + devicetype = DeviceType.objects.create( + model='Test DeviceType', + slug="test-devicetype", + manufacturer=manufacturer, + ) + + # Create an image file + image = BytesIO() + Image.new('RGB', (1, 1)).save(image, format='PNG') + image.name = 'test_image.png' + image.seek(0) + + # PATCH the DeviceType via REST API to add the image + data = { + 'front_image': image, + } + url = reverse('dcim-api:devicetype-detail', kwargs={'pk': devicetype.pk}) + self.add_permissions('dcim.change_devicetype') + + response = self.client.patch(url, data, format='multipart', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + + # Assert that the webhook job was enqueued cleanly + self.assertEqual(self.queue.count, 1) + job = self.queue.jobs[0] + self.assertEqual(job.kwargs['event_rule'], event_rule) + self.assertEqual(job.kwargs['event_type'], OBJECT_UPDATED) diff --git a/netbox/utilities/request.py b/netbox/utilities/request.py index 6471a4dec..f57e2e4b9 100644 --- a/netbox/utilities/request.py +++ b/netbox/utilities/request.py @@ -3,6 +3,7 @@ from contextlib import ExitStack, contextmanager from urllib.parse import urlparse from django.conf import settings +from django.utils.datastructures import MultiValueDict from django.utils.http import url_has_allowed_host_and_scheme from django.utils.translation import gettext_lazy as _ from netaddr import AddrFormatError, IPAddress @@ -66,6 +67,8 @@ def copy_safe_request(request, include_files=True): } if include_files: data['FILES'] = request.FILES + else: + data['FILES'] = MultiValueDict() return NetBoxFakeRequest(data) From 34f2ca6f849ad3fbd902590f281477114b846cc8 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 06:35:05 +0000 Subject: [PATCH 32/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 5afc5724c..86c863577 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-09 06:20+0000\n" +"POT-Creation-Date: 2026-06-10 06:34+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -16699,7 +16699,7 @@ msgstr "" msgid "Unknown app_label/model_name for {name}" msgstr "" -#: netbox/utilities/request.py:95 +#: netbox/utilities/request.py:98 #, python-brace-format msgid "Invalid IP address set for {header}: {ip}" msgstr "" From b4116f2532b1d1308668f05e7c03e62a3dfe2b11 Mon Sep 17 00:00:00 2001 From: mburggraf Date: Wed, 10 Jun 2026 18:27:50 +0200 Subject: [PATCH 33/58] Fixes #22273: Fix migration failure when a service has thousands of ports defined --- netbox/ipam/graphql/types.py | 4 +- .../0089_default_ordering_indexes.py | 5 +- .../0091_alter_service_index_and_ordering.py | 58 +++++++++++++++ netbox/ipam/models/services.py | 17 ++++- netbox/ipam/tests/test_models.py | 72 +++++++++++++++++++ 5 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 netbox/ipam/migrations/0091_alter_service_index_and_ordering.py diff --git a/netbox/ipam/graphql/types.py b/netbox/ipam/graphql/types.py index 31b93b419..f17c9c72e 100644 --- a/netbox/ipam/graphql/types.py +++ b/netbox/ipam/graphql/types.py @@ -244,7 +244,7 @@ class RouteTargetType(PrimaryObjectType): @strawberry_django.type( models.Service, - exclude=('parent_object_type', 'parent_object_id'), + exclude=('_ports_lowest', 'parent_object_type', 'parent_object_id'), filters=ServiceFilter, pagination=True ) @@ -264,7 +264,7 @@ class ServiceType(ContactsMixin, PrimaryObjectType): @strawberry_django.type( models.ServiceTemplate, - fields='__all__', + exclude=('_ports_lowest',), filters=ServiceTemplateFilter, pagination=True ) diff --git a/netbox/ipam/migrations/0089_default_ordering_indexes.py b/netbox/ipam/migrations/0089_default_ordering_indexes.py index a718aa3fc..64ec1b020 100644 --- a/netbox/ipam/migrations/0089_default_ordering_indexes.py +++ b/netbox/ipam/migrations/0089_default_ordering_indexes.py @@ -32,9 +32,12 @@ class Migration(migrations.Migration): model_name='role', index=models.Index(fields=['weight', 'name'], name='ipam_role_weight_01396b_idx'), ), + # Adding a dummy index, to allow a safe migration in case updating users already have services + # with a large number of ports configured (see issue #22273) + # Will get removed in 0091_alter_service_index_and_ordering migrations.AddIndex( model_name='service', - index=models.Index(fields=['protocol', 'ports', 'id'], name='ipam_servic_protoco_687d13_idx'), + index=models.Index(fields=['id'], name='ipam_servic_protoco_687d13_idx'), ), migrations.AddIndex( model_name='vlangroup', diff --git a/netbox/ipam/migrations/0091_alter_service_index_and_ordering.py b/netbox/ipam/migrations/0091_alter_service_index_and_ordering.py new file mode 100644 index 000000000..aa91e863c --- /dev/null +++ b/netbox/ipam/migrations/0091_alter_service_index_and_ordering.py @@ -0,0 +1,58 @@ +from django.db import migrations, models + + +def populate__ports_lowest(apps, schema_editor): + Service = apps.get_model('ipam', 'Service') + ServiceTemplate = apps.get_model('ipam', 'ServiceTemplate') + CHUNK_SIZE = 500 + + for model in (Service, ServiceTemplate): + chunk = [] + qs = model.objects.filter(_ports_lowest__isnull=True).only('id', 'ports', '_ports_lowest') + for obj in qs.iterator(chunk_size=CHUNK_SIZE): + if obj.ports: + obj._ports_lowest = min(obj.ports) + chunk.append(obj) + if len(chunk) >= CHUNK_SIZE: + model.objects.bulk_update(chunk, ['_ports_lowest']) + chunk = [] + if chunk: + model.objects.bulk_update(chunk, ['_ports_lowest']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0090_vlangroup_recompute_total_vlan_ids'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='service', + name='ipam_servic_protoco_687d13_idx', + ), + migrations.AddField( + model_name='service', + name='_ports_lowest', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='servicetemplate', + name='_ports_lowest', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.RunPython(populate__ports_lowest, migrations.RunPython.noop), + migrations.AddIndex( + model_name='service', + index=models.Index( + fields=['protocol', '_ports_lowest', 'id'], + name='ipam_servic_protoco_e2901d_idx' + ), + ), + migrations.AlterModelOptions( + name='service', + options={ + 'ordering': ('protocol', '_ports_lowest', 'id') + }, + ), + ] diff --git a/netbox/ipam/models/services.py b/netbox/ipam/models/services.py index d934465c3..ee12e3b04 100644 --- a/netbox/ipam/models/services.py +++ b/netbox/ipam/models/services.py @@ -31,10 +31,22 @@ class ServiceBase(models.Model): ), verbose_name=_('port numbers') ) + _ports_lowest = models.PositiveIntegerField( + null=True, + blank=True, + ) class Meta: abstract = True + def save(self, *args, **kwargs): + # On saving find the smallest port and save for default ordering + self._ports_lowest = min(self.ports) if self.ports else None + update_fields = kwargs.get('update_fields') + if update_fields is not None and '_ports_lowest' not in update_fields: + kwargs['update_fields'] = list(update_fields) + ['_ports_lowest'] + super().save(*args, **kwargs) + def __str__(self): return f'{self.name} ({self.get_protocol_display()}/{self.port_list})' @@ -74,7 +86,6 @@ class Service(ContactsMixin, ServiceBase, PrimaryModel): ct_field='parent_object_type', fk_field='parent_object_id' ) - name = models.CharField( max_length=100, verbose_name=_('name') @@ -93,9 +104,9 @@ class Service(ContactsMixin, ServiceBase, PrimaryModel): class Meta: indexes = ( - models.Index(fields=('protocol', 'ports', 'id')), # Default ordering + models.Index(fields=('protocol', '_ports_lowest', 'id')), # Default ordering models.Index(fields=('parent_object_type', 'parent_object_id')), ) - ordering = ('protocol', 'ports', 'pk') # (protocol, port) may be non-unique + ordering = ('protocol', '_ports_lowest', 'id') verbose_name = _('application service') verbose_name_plural = _('application services') diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index e5637433a..becbf9413 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -6,8 +6,10 @@ from netaddr import IPNetwork, IPSet from dcim.models import Site, SiteGroup from ipam.choices import * +from ipam.constants import SERVICE_PORT_MAX, SERVICE_PORT_MIN from ipam.models import * from utilities.data import string_to_ranges +from virtualization.models import VirtualMachine class AggregateTestCase(TestCase): @@ -926,3 +928,73 @@ class VLANTestCase(TestCase): vlan.group = vlangroups[2] with self.assertRaises(ValidationError): vlan.full_clean() + + +class ServiceTemplateTestCase(TestCase): + + def test_servicetemplate_lowest_port(self): + """ + Test lowest port setting for servicetemplate + """ + template = ServiceTemplate( + name='Template 1', + protocol=ServiceProtocolChoices.PROTOCOL_TCP, + ports=[80, 443, 22, 8080], # small test list + ) + template.full_clean() + template.save() + self.assertEqual(template._ports_lowest, 22) + + def test_servicetemplate_single_port(self): + """ + Test with a single port + """ + template = ServiceTemplate( + name='Template 2', + protocol=ServiceProtocolChoices.PROTOCOL_UDP, + ports=[53], + ) + template.full_clean() + template.save() + self.assertEqual(template._ports_lowest, 53) + + def test_servicetemplate_empty_ports(self): + """ + Test with empty ports list + """ + template = ServiceTemplate( + name='Template 3', + protocol=ServiceProtocolChoices.PROTOCOL_TCP, + ports=[], + ) + self.assertRaises(ValidationError, template.full_clean) + + +class ServiceTestCase(TestCase): + + @classmethod + def setUpTestData(cls): + site = Site.objects.create( + name='Site 1', + slug='site-1', + ) + VirtualMachine.objects.create( + name='virtual machine 1', + site=site, + ) + + def test_large_service(self): + """ + Test creation of service with large number of ports. + Related to issue #22273 + """ + service = Service( + name='Service 1', + protocol=ServiceProtocolChoices.PROTOCOL_TCP, + ports=list(range(SERVICE_PORT_MIN, SERVICE_PORT_MAX)), + parent=VirtualMachine.objects.first(), + ) + service.full_clean() + # Testing .save() is the important part, to check for database problems + service.save() + self.assertEqual(service._ports_lowest, SERVICE_PORT_MIN) From c63e3a8b80f977cde703fe02ae8cb145781f5d02 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 10 Jun 2026 12:48:35 -0400 Subject: [PATCH 34/58] Fixes #22421: GraphQLTestCase should support relative imports (#22422) --- netbox/utilities/testing/api.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/netbox/utilities/testing/api.py b/netbox/utilities/testing/api.py index 938c6a3b7..47a9e3b03 100644 --- a/netbox/utilities/testing/api.py +++ b/netbox/utilities/testing/api.py @@ -16,7 +16,6 @@ from django.contrib.postgres.fields import ArrayField from django.db import models from django.test import override_settings from django.urls import reverse -from django.utils.module_loading import import_string from rest_framework import status from rest_framework.test import APIClient from strawberry.types.base import StrawberryList, StrawberryOptional @@ -915,6 +914,7 @@ class APIViewTestCases: return None lazy_module = None + lazy_package = None # Cap iterations at 8: typical NetBox annotations nest at most 3 layers # (Union > Annotated > ForwardRef). 8 is a generous safety net to # prevent infinite loops on pathological / future annotation shapes. @@ -934,6 +934,9 @@ class APIViewTestCases: module_name = getattr(meta, 'module', None) if module_name: lazy_module = module_name + # strawberry.lazy('.relative') records the anchor package + # needed to resolve the leading-dot module path. + lazy_package = getattr(meta, 'package', None) break inner = args[0] if args else None if inner is None: @@ -947,9 +950,13 @@ class APIViewTestCases: if lazy_module is None: return None name = annotation.__forward_arg__ if isinstance(annotation, typing.ForwardRef) else annotation + # Resolve via import_module(module, package) rather than import_string() + # so relative lazy modules (e.g. strawberry.lazy('.filters')) resolve + # against their anchor package, as strawberry itself does. try: - return import_string(f'{lazy_module}.{name}') - except ImportError: + module = importlib.import_module(lazy_module, lazy_package) + return getattr(module, name) + except (ImportError, AttributeError): return None return annotation From 97a1375a82393f01620e411639893e519cef3fc3 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Wed, 10 Jun 2026 12:34:43 -0400 Subject: [PATCH 35/58] Security: replace random.choice with secrets.choice in Token.generate() Token.generate() used Python's random module (Mersenne Twister PRNG). Mersenne Twister is not a CSPRNG: observing ~624 outputs from the same worker process allows full state recovery and prediction of subsequent outputs. Any token minted in the same worker within that window becomes predictable, including tokens for privileged accounts. Fix: replace random.choice with secrets.choice. secrets is backed by os.urandom() / getrandom() which provides OS-level CSPRNG entropy and is immune to state-recovery attacks. The import of the now-unused random module is removed. Regression tests: - test_generate_uses_csprng: patches secrets.choice with wraps= to confirm it is called exactly TOKEN_DEFAULT_LENGTH times per generate(). - test_generate_length_parameter: verifies length= is respected and output is drawn only from TOKEN_CHARSET. Ref: SR-001 / VM-317 (internal security review, R1-F07 / R3-F1) Co-Authored-By: Claude Sonnet 4.6 --- netbox/users/models/tokens.py | 4 ++-- netbox/users/tests/test_models.py | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/netbox/users/models/tokens.py b/netbox/users/models/tokens.py index b47064c84..3c5afa94e 100644 --- a/netbox/users/models/tokens.py +++ b/netbox/users/models/tokens.py @@ -1,6 +1,6 @@ import hashlib import hmac -import random +import secrets import zoneinfo from django.conf import settings @@ -260,7 +260,7 @@ class Token(models.Model): """ Generate and return a random token value of the given length. """ - return ''.join(random.choice(TOKEN_CHARSET) for _ in range(length)) + return ''.join(secrets.choice(TOKEN_CHARSET) for _ in range(length)) def update_digest(self): """ diff --git a/netbox/users/tests/test_models.py b/netbox/users/tests/test_models.py index 7c1f5556e..fc59fe156 100644 --- a/netbox/users/tests/test_models.py +++ b/netbox/users/tests/test_models.py @@ -1,10 +1,13 @@ +import secrets from datetime import timedelta +from unittest.mock import patch from django.core.exceptions import ValidationError from django.test import TestCase, override_settings from django.utils import timezone from users.choices import TokenVersionChoices +from users.constants import TOKEN_CHARSET, TOKEN_DEFAULT_LENGTH from users.models import Token, User from utilities.testing import create_test_user @@ -104,6 +107,29 @@ class TokenTestCase(TestCase): with self.assertRaises(ValidationError): token.clean() + def test_generate_uses_csprng(self): + """ + Regression: Token.generate() must use secrets.choice (CSPRNG), not random.choice + (Mersenne Twister). Verify that the call is routed through the secrets module. + """ + with patch('users.models.tokens.secrets.choice', wraps=secrets.choice) as mock_choice: + value = Token.generate() + + self.assertEqual(mock_choice.call_count, TOKEN_DEFAULT_LENGTH, + "secrets.choice must be called once per token character") + self.assertEqual(len(value), TOKEN_DEFAULT_LENGTH) + self.assertTrue(all(c in TOKEN_CHARSET for c in value), + "Generated token must only contain characters from TOKEN_CHARSET") + + def test_generate_length_parameter(self): + """ + Token.generate(length=N) returns a string of exactly N characters from TOKEN_CHARSET. + """ + for length in (8, 20, TOKEN_DEFAULT_LENGTH, 64): + value = Token.generate(length=length) + self.assertEqual(len(value), length, f"Expected length {length}, got {len(value)}") + self.assertTrue(all(c in TOKEN_CHARSET for c in value)) + class UserConfigTestCase(TestCase): From d59f5f4381aeb75028ee526db188a47febc294e6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 10 Jun 2026 13:44:58 -0400 Subject: [PATCH 36/58] Fixes #22303: Annotate fields & omit parameters in OpenAPI schema --- netbox/core/api/schema.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/netbox/core/api/schema.py b/netbox/core/api/schema.py index 1105ca11a..4c63296d3 100644 --- a/netbox/core/api/schema.py +++ b/netbox/core/api/schema.py @@ -14,7 +14,7 @@ from drf_spectacular.plumbing import ( get_doc, ) from drf_spectacular.types import OpenApiTypes -from drf_spectacular.utils import Direction +from drf_spectacular.utils import Direction, OpenApiParameter from netbox.api.fields import ChoiceField from netbox.api.serializers import WritableNestedSerializer @@ -274,6 +274,37 @@ class NetBoxAutoSchema(AutoSchema): writable_class = self.writable_serializers[type(serializer)] return writable_class + def get_override_parameters(self): + params = super().get_override_parameters() + # Expose the ?fields, ?omit, and ?brief query parameters supported by NetBoxModelViewSet + # for all non-bulk GET operations (both list and detail). + if not self.is_bulk_action and self.method == 'GET': + params = list(params) + [ + OpenApiParameter( + name='fields', + location=OpenApiParameter.QUERY, + required=False, + type=OpenApiTypes.STR, + description='Comma-separated list of fields to include in the response. Example: `fields=id,name`.', + ), + OpenApiParameter( + name='omit', + location=OpenApiParameter.QUERY, + required=False, + type=OpenApiTypes.STR, + description='Comma-separated list of fields to exclude from the response. ' + 'Example: `omit=description,tags`.', + ), + OpenApiParameter( + name='brief', + location=OpenApiParameter.QUERY, + required=False, + type=OpenApiTypes.BOOL, + description='Return only brief fields for each object.', + ), + ] + return params + def get_filter_backends(self): # bulk operations don't have filter params if self.is_bulk_action: From 65454d30db5df96db2f05b93a8bbbec5a29c0fa8 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 06:32:06 +0000 Subject: [PATCH 37/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 86c863577..8d9271de2 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-10 06:34+0000\n" +"POT-Creation-Date: 2026-06-11 06:31+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1444,7 +1444,7 @@ msgstr "" #: netbox/extras/models/models.py:574 netbox/extras/models/models.py:706 #: netbox/extras/models/notifications.py:127 netbox/extras/models/scripts.py:29 #: netbox/ipam/models/asns.py:18 netbox/ipam/models/fhrp.py:24 -#: netbox/ipam/models/services.py:51 netbox/ipam/models/services.py:80 +#: netbox/ipam/models/services.py:63 netbox/ipam/models/services.py:91 #: netbox/ipam/models/vlans.py:42 netbox/ipam/models/vlans.py:231 #: netbox/ipam/models/vlans.py:380 netbox/ipam/models/vrfs.py:20 #: netbox/ipam/models/vrfs.py:78 netbox/netbox/models/__init__.py:180 @@ -11575,7 +11575,7 @@ msgstr "" msgid "Hostname or FQDN (not case-sensitive)" msgstr "" -#: netbox/ipam/models/ip.py:852 netbox/ipam/models/services.py:86 +#: netbox/ipam/models/ip.py:852 netbox/ipam/models/services.py:97 msgid "IP addresses" msgstr "" @@ -11623,24 +11623,24 @@ msgstr "" msgid "port numbers" msgstr "" -#: netbox/ipam/models/services.py:58 +#: netbox/ipam/models/services.py:70 msgid "application service template" msgstr "" -#: netbox/ipam/models/services.py:59 +#: netbox/ipam/models/services.py:71 msgid "application service templates" msgstr "" -#: netbox/ipam/models/services.py:87 +#: netbox/ipam/models/services.py:98 msgid "" "The specific IP addresses (if any) to which this application service is bound" msgstr "" -#: netbox/ipam/models/services.py:100 +#: netbox/ipam/models/services.py:111 msgid "application service" msgstr "" -#: netbox/ipam/models/services.py:101 +#: netbox/ipam/models/services.py:112 msgid "application services" msgstr "" From d1919627cefc93ba5104f327df0f2959396d129f Mon Sep 17 00:00:00 2001 From: bctiemann Date: Thu, 11 Jun 2026 16:30:27 -0400 Subject: [PATCH 38/58] Closes #22429: Enforce ObjectPermission constraints on grant_token (#22424) --- netbox/users/api/serializers_/tokens.py | 5 +- netbox/users/forms/model_forms.py | 3 +- netbox/users/tests/test_api.py | 103 ++++++++++++++++++++++++ netbox/users/tests/test_views.py | 72 +++++++++++++++++ netbox/users/utils.py | 61 ++++++++++++++ netbox/users/views.py | 3 +- 6 files changed, 244 insertions(+), 3 deletions(-) diff --git a/netbox/users/api/serializers_/tokens.py b/netbox/users/api/serializers_/tokens.py index ce467e1a6..1a140e901 100644 --- a/netbox/users/api/serializers_/tokens.py +++ b/netbox/users/api/serializers_/tokens.py @@ -5,6 +5,7 @@ from rest_framework.exceptions import AuthenticationFailed, PermissionDenied from netbox.api.fields import IPNetworkSerializer from netbox.api.serializers import ValidatedModelSerializer from users.models import Token +from users.utils import user_may_grant_token from .users import * @@ -50,9 +51,11 @@ class TokenSerializer(ValidatedModelSerializer): def validate(self, data): # If the Token is being created on behalf of another user, enforce the grant_token permission. + # Use user_may_grant_token() rather than has_perm(obj=None): the latter short-circuits to True + # when the permission is present in cache without evaluating ObjectPermission constraints. request = self.context.get('request') token_user = data.get('user') - if token_user and token_user != request.user and not request.user.has_perm('users.grant_token'): + if token_user and token_user != request.user and not user_may_grant_token(request.user, token_user): raise PermissionDenied("This user does not have permission to create tokens for other users.") return super().validate(data) diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py index b86eb7234..395391515 100644 --- a/netbox/users/forms/model_forms.py +++ b/netbox/users/forms/model_forms.py @@ -18,6 +18,7 @@ from netbox.registry import registry from users.choices import TokenVersionChoices from users.constants import * from users.models import * +from users.utils import user_may_grant_token from utilities.data import flatten_dict from utilities.forms.fields import ( ContentTypeMultipleChoiceField, @@ -195,7 +196,7 @@ class TokenForm(UserTokenForm): if request is None: # Fail closed: we cannot verify that the acting user is authorized. raise forms.ValidationError(_("Unable to verify permission to create tokens.")) - if token_user != request.user and not request.user.has_perm('users.grant_token'): + if token_user != request.user and not user_may_grant_token(request.user, token_user): raise forms.ValidationError( _("This user does not have permission to create tokens for other users.") ) diff --git a/netbox/users/tests/test_api.py b/netbox/users/tests/test_api.py index a50ce74bc..de57d8696 100644 --- a/netbox/users/tests/test_api.py +++ b/netbox/users/tests/test_api.py @@ -310,6 +310,109 @@ class TokenTestCase( response = self.client.post(url, data, format='json', **self.header) self.assertEqual(response.status_code, 201) + def test_grant_token_constrained_permission_is_enforced(self): + """ + Regression: SR-001 / VM-322 — constrained grant_token ObjectPermissions must not be + bypassed. has_perm('users.grant_token', obj=None) short-circuits to True without + evaluating constraints; the fix uses user_may_grant_token() which applies them. + """ + # Clear the unconstrained grant_token added by setUp. + ObjectPermission.objects.filter(users=self.user, actions__contains=['grant']).delete() + self.add_permissions('users.add_token') + + superuser = User.objects.create_user(username='sec_superuser', is_superuser=True) + regular = User.objects.create_user(username='sec_regular') + + # Add a *constrained* grant_token permission: only tokens for non-superusers. + token_ct = ObjectType.objects.get_by_natural_key('users', 'token') + perm = ObjectPermission( + name='constrained_grant_token', + constraints={'user__is_superuser': False}, + actions=['grant'], + ) + perm.save() + perm.users.add(self.user) + perm.object_types.add(token_ct) + + url = reverse('users-api:token-list') + + # Attempt to create a token for the superuser — must be denied (constraint violation). + response = self.client.post(url, {'user': superuser.pk}, format='json', **self.header) + self.assertEqual( + response.status_code, 403, + "Constrained grant_token must deny token creation for users that violate the constraint", + ) + + # Attempt to create a token for the regular user — must succeed. + response = self.client.post(url, {'user': regular.pk}, format='json', **self.header) + self.assertEqual( + response.status_code, 201, + "Constrained grant_token must allow token creation for users that satisfy the constraint", + ) + + def test_grant_token_superuser_always_allowed(self): + """ + Superusers must be able to create tokens for any user without an explicit + grant_token ObjectPermission. Regression guard: _user_may_grant_token() must + mirror ObjectPermissionMixin.has_perm's superuser bypass. + """ + ObjectPermission.objects.filter(users=self.user, actions__contains=['grant']).delete() + self.add_permissions('users.add_token') + self.user.is_superuser = True + self.user.save() + try: + other = User.objects.create_user(username='superuser_grant_target') + url = reverse('users-api:token-list') + response = self.client.post(url, {'user': other.pk}, format='json', **self.header) + self.assertEqual(response.status_code, 201, "Superuser must be able to grant tokens for any user") + finally: + self.user.is_superuser = False + self.user.save() + + def test_grant_token_self_only_constraint(self): + """ + A {"user": "$user"} constraint means "only grant tokens for yourself". + Since creating a token for oneself bypasses the grant_token check entirely, + this constraint effectively blocks all cross-user grants for the holder. + Exercises the $user placeholder substitution path. + """ + ObjectPermission.objects.filter(users=self.user, actions__contains=['grant']).delete() + self.add_permissions('users.add_token') + token_ct = ObjectType.objects.get_by_natural_key('users', 'token') + perm = ObjectPermission(name='self_only_grant', constraints={'user': '$user'}, actions=['grant']) + perm.save() + perm.users.add(self.user) + perm.object_types.add(token_ct) + + other = User.objects.create_user(username='self_only_target') + url = reverse('users-api:token-list') + response = self.client.post(url, {'user': other.pk}, format='json', **self.header) + self.assertEqual(response.status_code, 403, "Self-only constraint must deny cross-user token grants") + + def test_grant_token_non_user_field_constraint_fails_closed(self): + """ + A constraint referencing a non-user Token field (e.g. {"write_enabled": True}) + cannot be evaluated for an unsaved token; _user_may_grant_token() must fail + closed and deny rather than bypass the constraint. + """ + ObjectPermission.objects.filter(users=self.user, actions__contains=['grant']).delete() + self.add_permissions('users.add_token') + token_ct = ObjectType.objects.get_by_natural_key('users', 'token') + perm = ObjectPermission( + name='non_user_field_grant', constraints={'write_enabled': True}, actions=['grant'] + ) + perm.save() + perm.users.add(self.user) + perm.object_types.add(token_ct) + + other = User.objects.create_user(username='non_user_field_target') + url = reverse('users-api:token-list') + response = self.client.post(url, {'user': other.pk}, format='json', **self.header) + self.assertEqual( + response.status_code, 403, + "Non-user Token field constraints must fail closed for new (unsaved) tokens", + ) + def test_create_token_returns_plaintext(self): """ Creating a Token via the REST API must return the usable plaintext value in the response. diff --git a/netbox/users/tests/test_views.py b/netbox/users/tests/test_views.py index 97a91ab79..7eee5ff0e 100644 --- a/netbox/users/tests/test_views.py +++ b/netbox/users/tests/test_views.py @@ -463,6 +463,78 @@ class TokenGrantPermissionTestCase(TestCase): self.assertEqual(token.user, owner) self.assertEqual(token.description, 'updated') + def _add_constrained_grant_permission(self, constraints): + """Add a constrained grant_token ObjectPermission to self.user.""" + token_ct = ObjectType.objects.get_by_natural_key('users', 'token') + perm = ObjectPermission( + name='constrained_grant_token', + constraints=constraints, + actions=['grant'], + ) + perm.save() + perm.users.add(self.user) + perm.object_types.add(token_ct) + + def test_create_token_via_form_constrained_grant_is_enforced(self): + """ + Regression: SR-001 / VM-322 — constrained grant_token ObjectPermissions must not + be bypassed via the UI create form. has_perm(obj=None) short-circuits to True + without evaluating constraints; user_may_grant_token() applies them correctly. + """ + superuser = User.objects.create_user(username='form_constrained_super', is_superuser=True) + regular = User.objects.create_user(username='form_constrained_regular') + self._add_constrained_grant_permission({'user__is_superuser': False}) + + # Constrained grant must deny token creation for a superuser. + response = self.client.post(reverse('users:token_add'), data={ + 'version': 2, + 'user': superuser.pk, + 'description': 'constrained denied', + 'enabled': 'on', + }) + self.assertEqual(response.status_code, 200) + self.assertFalse(Token.objects.filter(user=superuser).exists()) + + # Constrained grant must allow token creation for a non-superuser. + response = self.client.post(reverse('users:token_add'), data={ + 'version': 2, + 'user': regular.pk, + 'description': 'constrained allowed', + 'enabled': 'on', + }) + self.assertEqual(response.status_code, 302) + self.assertTrue(Token.objects.filter(description='constrained allowed', user=regular).exists()) + + def test_bulk_import_token_constrained_grant_is_enforced(self): + """ + Regression: SR-001 / VM-322 — constrained grant_token ObjectPermissions must not + be bypassed via bulk CSV import. has_perm(obj=None) short-circuits to True + without evaluating constraints; user_may_grant_token() applies them correctly. + """ + superuser = User.objects.create_user(username='bulk_constrained_super', is_superuser=True) + regular = User.objects.create_user(username='bulk_constrained_regular') + self._add_constrained_grant_permission({'user__is_superuser': False}) + + # Constrained grant must deny bulk import for a superuser. + csv_data = '\n'.join(('user,description', f"{superuser.pk},bulk denied")) + response = self.client.post(reverse('users:token_bulk_import'), data={ + 'data': csv_data, + 'format': ImportFormatChoices.CSV, + 'csv_delimiter': CSVDelimiterChoices.AUTO, + }) + self.assertEqual(response.status_code, 200) + self.assertFalse(Token.objects.filter(user=superuser).exists()) + + # Constrained grant must allow bulk import for a non-superuser. + csv_data = '\n'.join(('user,description', f"{regular.pk},bulk allowed")) + response = self.client.post(reverse('users:token_bulk_import'), data={ + 'data': csv_data, + 'format': ImportFormatChoices.CSV, + 'csv_delimiter': CSVDelimiterChoices.AUTO, + }) + self.assertEqual(response.status_code, 302) + self.assertTrue(Token.objects.filter(description='bulk allowed', user=regular).exists()) + class OwnerGroupTestCase(ViewTestCases.AdminModelViewTestCase): model = OwnerGroup diff --git a/netbox/users/utils.py b/netbox/users/utils.py index c355873a8..d585cfa06 100644 --- a/netbox/users/utils.py +++ b/netbox/users/utils.py @@ -1,12 +1,73 @@ from django.conf import settings +from django.db.models import Q from social_core.storage import NO_ASCII_REGEX, NO_SPECIAL_REGEX __all__ = ( 'clean_username', 'get_current_pepper', + 'user_may_grant_token', ) +def user_may_grant_token(requesting_user, token_user): + """ + Return True if *requesting_user* has permission to create a token for *token_user*, + respecting ObjectPermission constraints on the users.grant_token permission. + + ``has_perm('users.grant_token', obj=None)`` always short-circuits to True when the + permission is present in the cache (obj=None bypasses constraint evaluation in + ObjectPermissionMixin.has_perm). Since the new token does not yet exist in the + database we cannot pass an existing Token as obj. Instead we extract the raw + constraint list, remap Token-field paths to User-field paths, and evaluate them + directly against the target User record — the only variable in a new token creation. + + Field remapping rules: + ``user__`` → ```` (FK traversal into User) + ``user`` → ``pk`` (Token.user FK becomes User.pk) + + Constraints referencing other Token fields cannot be evaluated for an unsaved + token; the function returns False (deny) for those. + """ + from users.models import User + + # Mirrors ObjectPermissionMixin.has_perm: superusers implicitly have all permissions. + if requesting_user.is_active and requesting_user.is_superuser: + return True + + perm = 'users.grant_token' + + # get_all_permissions() populates _object_perm_cache as a side effect. + # Guard against missing cache key in case a non-standard backend is in use. + if perm not in requesting_user.get_all_permissions(): + return False + + constraints = getattr(requesting_user, '_object_perm_cache', {}).get(perm, []) + + # An empty/null constraint means "no restriction" — allow any target. + if any(not c for c in constraints): + return True + + # Substitute the $user token so {"user": "$user"} resolves to the requesting user. + resolved_user_id = requesting_user.pk + + q = Q() + for constraint in constraints: + user_constraint = {} + for key, raw_val in constraint.items(): + val = resolved_user_id if raw_val == '$user' else raw_val + if key == 'user': + user_constraint['pk'] = val + elif key.startswith('user__'): + user_constraint[key.removeprefix('user__')] = val + else: + # Non-user Token field — cannot evaluate for a new (unsaved) token. + # Fail closed. + return False + q |= Q(**user_constraint) + + return User.objects.filter(q, pk=token_user.pk).exists() + + def clean_username(value): """Clean username removing any unsupported character""" value = NO_ASCII_REGEX.sub('', value) diff --git a/netbox/users/views.py b/netbox/users/views.py index 6c0f963a7..0fc140e9e 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -16,6 +16,7 @@ from netbox.ui.panels import ( ) from netbox.views import generic from users.ui import panels +from users.utils import user_may_grant_token from utilities.query import count_related from utilities.views import GetRelatedModelsMixin, register_model_view @@ -86,7 +87,7 @@ class TokenBulkImportView(generic.BulkImportView): # creation; TokenImportForm disables the user field on update, so an existing Token's owner cannot change. token_user = object_form.cleaned_data.get('user') if object_form.instance._state.adding and token_user and token_user != request.user \ - and not request.user.has_perm('users.grant_token'): + and not user_may_grant_token(request.user, token_user): raise ValidationError(_("This user does not have permission to create tokens for other users.")) return super().save_object(object_form, request) From acef3ac1128a7a6a46b28dc86d32a395402b2842 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 06:31:01 +0000 Subject: [PATCH 39/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 98 ++++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 8d9271de2..d8bbc6858 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-11 06:31+0000\n" +"POT-Creation-Date: 2026-06-12 06:30+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -566,7 +566,7 @@ msgstr "" #: netbox/ipam/tables/vlans.py:102 netbox/templates/core/rq_task.html:81 #: netbox/templates/core/system.html:20 #: netbox/templates/extras/inc/script_list_content.html:35 -#: netbox/users/forms/filtersets.py:36 netbox/users/forms/model_forms.py:232 +#: netbox/users/forms/filtersets.py:36 netbox/users/forms/model_forms.py:233 #: netbox/virtualization/forms/bulk_edit.py:62 #: netbox/virtualization/forms/bulk_edit.py:115 #: netbox/virtualization/forms/bulk_import.py:57 @@ -1190,8 +1190,8 @@ msgstr "" #: netbox/ipam/forms/filtersets.py:154 netbox/ipam/forms/filtersets.py:663 #: netbox/ipam/forms/model_forms.py:353 netbox/ipam/ui/panels.py:122 #: netbox/templates/ipam/vlan_edit.html:42 -#: netbox/tenancy/forms/filtersets.py:116 netbox/users/forms/model_forms.py:387 -#: netbox/users/forms/model_forms.py:429 +#: netbox/tenancy/forms/filtersets.py:116 netbox/users/forms/model_forms.py:388 +#: netbox/users/forms/model_forms.py:430 msgid "Assignment" msgstr "" @@ -1216,7 +1216,7 @@ msgstr "" #: netbox/users/forms/bulk_edit.py:161 netbox/users/forms/filtersets.py:35 #: netbox/users/forms/filtersets.py:41 netbox/users/forms/filtersets.py:76 #: netbox/users/forms/filtersets.py:165 netbox/users/forms/filtersets.py:171 -#: netbox/users/forms/model_forms.py:560 netbox/users/tables.py:186 +#: netbox/users/forms/model_forms.py:561 netbox/users/tables.py:186 #: netbox/virtualization/forms/bulk_edit.py:57 #: netbox/virtualization/forms/bulk_import.py:50 #: netbox/virtualization/forms/filtersets.py:102 @@ -1985,7 +1985,7 @@ msgstr "" #: netbox/core/data_backends.py:69 netbox/templates/account/base.html:23 #: netbox/templates/account/password.html:12 -#: netbox/users/forms/model_forms.py:208 +#: netbox/users/forms/model_forms.py:209 msgid "Password" msgstr "" @@ -2159,8 +2159,8 @@ msgstr "" #: netbox/extras/tables/tables.py:407 netbox/extras/tables/tables.py:448 #: netbox/templates/inc/user_menu.html:31 netbox/users/filtersets.py:135 #: netbox/users/filtersets.py:217 netbox/users/forms/filtersets.py:81 -#: netbox/users/forms/filtersets.py:126 netbox/users/forms/model_forms.py:173 -#: netbox/users/forms/model_forms.py:230 netbox/users/tables.py:22 +#: netbox/users/forms/filtersets.py:126 netbox/users/forms/model_forms.py:174 +#: netbox/users/forms/model_forms.py:231 netbox/users/tables.py:22 msgid "User" msgstr "" @@ -2266,7 +2266,7 @@ msgstr "" #: netbox/core/forms/model_forms.py:171 netbox/dcim/forms/filtersets.py:874 #: netbox/templates/core/inc/config_data.html:140 -#: netbox/users/forms/model_forms.py:77 +#: netbox/users/forms/model_forms.py:78 msgid "Miscellaneous" msgstr "" @@ -5920,7 +5920,7 @@ msgstr "" #: netbox/dcim/forms/object_create.py:312 netbox/dcim/tables/devices.py:1160 #: netbox/ipam/tables/fhrp.py:31 netbox/ipam/ui/panels.py:185 #: netbox/ipam/views.py:1583 netbox/templates/dcim/virtualchassis_edit.html:59 -#: netbox/users/views.py:374 +#: netbox/users/views.py:375 msgid "Members" msgstr "" @@ -8832,7 +8832,7 @@ msgstr "" #: netbox/extras/forms/model_forms.py:338 #: netbox/extras/forms/model_forms.py:381 #: netbox/extras/forms/model_forms.py:554 -#: netbox/extras/forms/model_forms.py:671 netbox/users/forms/model_forms.py:335 +#: netbox/extras/forms/model_forms.py:671 netbox/users/forms/model_forms.py:336 msgid "Object types" msgstr "" @@ -8977,9 +8977,9 @@ msgstr "" #: netbox/extras/forms/bulk_import.py:350 #: netbox/extras/forms/model_forms.py:502 netbox/extras/ui/panels.py:321 #: netbox/netbox/navigation/menu.py:423 netbox/users/forms/filtersets.py:181 -#: netbox/users/forms/model_forms.py:274 netbox/users/forms/model_forms.py:286 -#: netbox/users/forms/model_forms.py:361 netbox/users/forms/model_forms.py:557 -#: netbox/users/forms/model_forms.py:572 netbox/users/tables.py:136 +#: netbox/users/forms/model_forms.py:275 netbox/users/forms/model_forms.py:287 +#: netbox/users/forms/model_forms.py:362 netbox/users/forms/model_forms.py:558 +#: netbox/users/forms/model_forms.py:573 netbox/users/tables.py:136 #: netbox/users/tables.py:194 msgid "Users" msgstr "" @@ -8994,9 +8994,9 @@ msgstr "" #: netbox/tenancy/forms/bulk_edit.py:121 netbox/tenancy/forms/filtersets.py:107 #: netbox/tenancy/forms/model_forms.py:93 netbox/tenancy/tables/contacts.py:57 #: netbox/tenancy/tables/contacts.py:101 netbox/tenancy/ui/panels.py:12 -#: netbox/users/forms/filtersets.py:176 netbox/users/forms/model_forms.py:219 -#: netbox/users/forms/model_forms.py:231 netbox/users/forms/model_forms.py:366 -#: netbox/users/forms/model_forms.py:556 netbox/users/tables.py:68 +#: netbox/users/forms/filtersets.py:176 netbox/users/forms/model_forms.py:220 +#: netbox/users/forms/model_forms.py:232 netbox/users/forms/model_forms.py:367 +#: netbox/users/forms/model_forms.py:557 netbox/users/tables.py:68 #: netbox/users/tables.py:140 netbox/users/tables.py:190 msgid "Groups" msgstr "" @@ -10278,7 +10278,7 @@ msgstr "" #: netbox/netbox/forms/mixins.py:166 netbox/netbox/forms/mixins.py:191 #: netbox/netbox/tables/tables.py:311 netbox/netbox/tables/tables.py:326 #: netbox/netbox/tables/tables.py:341 netbox/templates/generic/object.html:61 -#: netbox/users/forms/model_forms.py:555 +#: netbox/users/forms/model_forms.py:556 msgid "Owner" msgstr "" @@ -12490,7 +12490,7 @@ msgstr "" #: netbox/netbox/forms/mixins.py:182 netbox/netbox/tables/tables.py:307 #: netbox/netbox/tables/tables.py:322 netbox/netbox/tables/tables.py:337 -#: netbox/users/forms/model_forms.py:543 +#: netbox/users/forms/model_forms.py:544 msgid "Owner Group" msgstr "" @@ -12901,9 +12901,9 @@ msgstr "" msgid "API Tokens" msgstr "" -#: netbox/netbox/navigation/menu.py:426 netbox/users/forms/model_forms.py:225 -#: netbox/users/forms/model_forms.py:233 netbox/users/forms/model_forms.py:280 -#: netbox/users/forms/model_forms.py:287 +#: netbox/netbox/navigation/menu.py:426 netbox/users/forms/model_forms.py:226 +#: netbox/users/forms/model_forms.py:234 netbox/users/forms/model_forms.py:281 +#: netbox/users/forms/model_forms.py:288 msgid "Permissions" msgstr "" @@ -13598,8 +13598,8 @@ msgstr "" msgid "Superuser" msgstr "" -#: netbox/templates/account/profile.html:47 netbox/users/views.py:128 -#: netbox/users/views.py:310 +#: netbox/templates/account/profile.html:47 netbox/users/views.py:129 +#: netbox/users/views.py:311 msgid "Assigned Groups" msgstr "" @@ -14625,8 +14625,8 @@ msgstr "" #: netbox/templates/dcim/virtualchassis_add_member.html:27 #: netbox/templates/generic/object_edit.html:80 -#: netbox/users/forms/filtersets.py:64 netbox/users/forms/model_forms.py:385 -#: netbox/users/forms/model_forms.py:427 netbox/users/ui/panels.py:50 +#: netbox/users/forms/filtersets.py:64 netbox/users/forms/model_forms.py:386 +#: netbox/users/forms/model_forms.py:428 netbox/users/ui/panels.py:50 msgid "Actions" msgstr "" @@ -15542,11 +15542,11 @@ msgid "" " " msgstr "" -#: netbox/templates/users/inc/user_activity.html:6 netbox/users/views.py:142 +#: netbox/templates/users/inc/user_activity.html:6 netbox/users/views.py:143 msgid "Recent Activity" msgstr "" -#: netbox/templates/users/inc/user_activity.html:9 netbox/users/views.py:147 +#: netbox/templates/users/inc/user_activity.html:9 netbox/users/views.py:148 msgid "View All" msgstr "" @@ -15566,7 +15566,7 @@ msgid "Permission" msgstr "" #: netbox/templates/users/panels/actions.html:27 -#: netbox/users/forms/model_forms.py:355 +#: netbox/users/forms/model_forms.py:356 msgid "Additional actions" msgstr "" @@ -15939,7 +15939,7 @@ msgstr "" msgid "Expires" msgstr "" -#: netbox/users/forms/bulk_edit.py:125 netbox/users/forms/model_forms.py:130 +#: netbox/users/forms/bulk_edit.py:125 netbox/users/forms/model_forms.py:131 #: netbox/users/tables.py:47 netbox/users/ui/panels.py:17 msgid "Allowed IPs" msgstr "" @@ -15984,72 +15984,72 @@ msgstr "" msgid "Membership" msgstr "" -#: netbox/users/forms/model_forms.py:75 +#: netbox/users/forms/model_forms.py:76 msgid "User Interface" msgstr "" -#: netbox/users/forms/model_forms.py:132 +#: netbox/users/forms/model_forms.py:133 msgid "" "Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for " "no restrictions. Example: 10.1.1.0/24,192.168.10.16/32,2001:" "db8:1::/64" msgstr "" -#: netbox/users/forms/model_forms.py:197 +#: netbox/users/forms/model_forms.py:198 msgid "Unable to verify permission to create tokens." msgstr "" -#: netbox/users/forms/model_forms.py:200 netbox/users/views.py:90 +#: netbox/users/forms/model_forms.py:201 netbox/users/views.py:91 msgid "This user does not have permission to create tokens for other users." msgstr "" -#: netbox/users/forms/model_forms.py:213 +#: netbox/users/forms/model_forms.py:214 msgid "Confirm password" msgstr "" -#: netbox/users/forms/model_forms.py:216 +#: netbox/users/forms/model_forms.py:217 msgid "Enter the same password as before, for verification." msgstr "" -#: netbox/users/forms/model_forms.py:265 +#: netbox/users/forms/model_forms.py:266 msgid "Passwords do not match! Please check your input and try again." msgstr "" -#: netbox/users/forms/model_forms.py:340 +#: netbox/users/forms/model_forms.py:341 msgid "Select the types of objects to which the permission will apply." msgstr "" -#: netbox/users/forms/model_forms.py:358 +#: netbox/users/forms/model_forms.py:359 msgid "" "Additional actions for models which have not yet registered their own actions" msgstr "" -#: netbox/users/forms/model_forms.py:372 netbox/users/forms/model_forms.py:388 -#: netbox/users/forms/model_forms.py:430 netbox/users/views.py:302 +#: netbox/users/forms/model_forms.py:373 netbox/users/forms/model_forms.py:389 +#: netbox/users/forms/model_forms.py:431 netbox/users/views.py:303 msgid "Constraints" msgstr "" -#: netbox/users/forms/model_forms.py:374 +#: netbox/users/forms/model_forms.py:375 msgid "" "JSON expression of a queryset filter that will return only permitted " "objects. Leave null to match all objects of this type. A list of multiple " "objects will result in a logical OR operation." msgstr "" -#: netbox/users/forms/model_forms.py:382 netbox/users/forms/model_forms.py:422 +#: netbox/users/forms/model_forms.py:383 netbox/users/forms/model_forms.py:423 msgid "Objects" msgstr "" -#: netbox/users/forms/model_forms.py:509 +#: netbox/users/forms/model_forms.py:510 msgid "At least one action must be selected." msgstr "" -#: netbox/users/forms/model_forms.py:527 +#: netbox/users/forms/model_forms.py:528 #, python-brace-format msgid "Invalid filter for {model}: {error}" msgstr "" -#: netbox/users/forms/model_forms.py:567 +#: netbox/users/forms/model_forms.py:568 msgid "User groups" msgstr "" @@ -16264,15 +16264,15 @@ msgstr "" msgid "View" msgstr "" -#: netbox/users/views.py:132 netbox/users/views.py:233 +#: netbox/users/views.py:133 netbox/users/views.py:234 msgid "Assigned Permissions" msgstr "" -#: netbox/users/views.py:136 netbox/users/views.py:237 +#: netbox/users/views.py:137 netbox/users/views.py:238 msgid "Owner Membership" msgstr "" -#: netbox/users/views.py:307 +#: netbox/users/views.py:308 msgid "Assigned Users" msgstr "" From 8f974e3cc8e91e6522583ffb1ef00ddedf0122fe Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Sat, 13 Jun 2026 15:45:44 +0200 Subject: [PATCH 40/58] perf(ipam): Optimize Prefix availability calculations Replace IPSet-heavy Prefix availability and utilization logic with indexed host lookups, distinct host counts, and interval-based availability calculation. This adds mask-insensitive host-bound filtering for IP addresses and ranges, moves availability/counting behavior onto QuerySet and model methods, and uses merged occupied intervals to find available addresses without materializing large address sets in Python. Prefix utilization remains on a cheap utilization-only path for list views, while Prefix detail views can use a shared usage summary when both utilization and available IP count are needed. Usable IP bounds now live on the Prefix model, since the logic depends on Prefix-specific state such as is_pool. This also adds host expression indexes for IP Ranges, fixes zero-address preparation, fixes child IP matching across differing mask lengths, keeps Prefix hierarchy rebuilding scoped to the existing VRF/global API, and preserves IPRange.first_available_ip as a cached compatibility wrapper. Fixes #21870 --- netbox/ipam/api/views.py | 2 +- netbox/ipam/fields.py | 6 +- netbox/ipam/lookups.py | 35 +- netbox/ipam/managers.py | 4 +- .../migrations/0092_iprange_host_indexes.py | 34 + netbox/ipam/models/ip.py | 339 +++++-- netbox/ipam/querysets.py | 191 +++- netbox/ipam/tests/test_fields.py | 29 + netbox/ipam/tests/test_lookups.py | 135 ++- netbox/ipam/tests/test_models.py | 924 +++++++++++++++++- netbox/ipam/tests/test_querysets.py | 344 +++++++ netbox/ipam/utils.py | 33 +- .../templates/ipam/iprange/ip_addresses.html | 12 +- .../ipam/panels/prefix_addressing.html | 48 +- 14 files changed, 1996 insertions(+), 140 deletions(-) create mode 100644 netbox/ipam/migrations/0092_iprange_host_indexes.py create mode 100644 netbox/ipam/tests/test_fields.py create mode 100644 netbox/ipam/tests/test_querysets.py diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 14f39903f..cdc6786f0 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -407,7 +407,7 @@ class AvailableIPAddressesView(AvailableObjectsView): def get_available_objects(self, parent, limit=None): # Calculate available IPs within the parent ip_list = [] - for index, ip in enumerate(parent.get_available_ips(), start=1): + for index, ip in enumerate(parent.iter_available_ips(), start=1): ip_list.append(ip) if index == limit: break diff --git a/netbox/ipam/fields.py b/netbox/ipam/fields.py index b83c4ca30..11fa5bac0 100644 --- a/netbox/ipam/fields.py +++ b/netbox/ipam/fields.py @@ -42,7 +42,10 @@ class BaseIPField(models.Field): raise ValidationError(e) def get_prep_value(self, value): - if not value: + # Membership check; `not value` incorrectly treats the valid zero addresses + # 0.0.0.0 and :: as empty. netaddr objects compare unequal to all three + # sentinels; raw int 0 stays "empty" for backward compatibility. + if value in (None, '', 0): return None if isinstance(value, list): return [str(self.to_python(v)) for v in value] @@ -107,6 +110,7 @@ IPAddressField.register_lookup(lookups.NetContainsOrEquals) IPAddressField.register_lookup(lookups.NetHost) IPAddressField.register_lookup(lookups.NetIn) IPAddressField.register_lookup(lookups.NetHostContained) +IPAddressField.register_lookup(lookups.NetHostBetween) IPAddressField.register_lookup(lookups.NetFamily) IPAddressField.register_lookup(lookups.NetMaskLength) IPAddressField.register_lookup(lookups.Host) diff --git a/netbox/ipam/lookups.py b/netbox/ipam/lookups.py index 693903f3f..309917699 100644 --- a/netbox/ipam/lookups.py +++ b/netbox/ipam/lookups.py @@ -1,3 +1,4 @@ +import netaddr from django.db.models import IntegerField, Lookup, Transform, lookups @@ -99,7 +100,8 @@ class NetHost(Lookup): if rhs_params: rhs_params[0] = rhs_params[0].split('/')[0] params = list(lhs_params) + rhs_params - return f'HOST({lhs}) = {rhs}', params + # Cast to INET so the predicate matches the inet ipam_ipaddress_host index. + return f'CAST(HOST({lhs}) AS INET) = {rhs}', params class NetIn(Lookup): @@ -120,7 +122,8 @@ class NetIn(Lookup): without_mask.append(address) address_in_clause = self.create_in_clause('{} IN ('.format(lhs), len(with_mask)) - host_in_clause = self.create_in_clause('HOST({}) IN ('.format(lhs), len(without_mask)) + # Cast to INET so the predicate matches the inet ipam_ipaddress_host index. + host_in_clause = self.create_in_clause('CAST(HOST({}) AS INET) IN ('.format(lhs), len(without_mask)) if with_mask and not without_mask: return address_in_clause, with_mask @@ -156,6 +159,34 @@ class NetHostContained(Lookup): return f'CAST(HOST({lhs}) AS INET) <<= {rhs}', params +class NetHostBetween(Lookup): + """ + Match host addresses (mask ignored) falling inclusively between two bounds. The left-hand + side is kept as an inet-typed host expression so PostgreSQL can use the host expression + indexes on the IPAM address and range tables; the CAST(HOST(...) AS INET) spelling matches + NetHost/NetIn for consistency (PostgreSQL canonicalizes the INET(HOST(...)) function form + to the same expression). + """ + lookup_name = 'host_between' + + def get_prep_lookup(self): + if not isinstance(self.rhs, (list, tuple)) or len(self.rhs) != 2: + raise ValueError('The host_between lookup requires a (lower, upper) pair of bounds') + try: + # Normalize to bare hosts; reject malformed values before they reach SQL. + lower, upper = (netaddr.IPNetwork(str(bound)).ip for bound in self.rhs) + except (netaddr.AddrFormatError, ValueError) as e: + raise ValueError(f'Invalid host_between bound: {e}') from e + if lower.version != upper.version: + raise ValueError('host_between bounds must not mix address families') + return lower, upper + + def as_sql(self, qn, connection): + lhs, lhs_params = self.process_lhs(qn, connection) + params = list(lhs_params) + [str(bound) for bound in self.rhs] + return f'CAST(HOST({lhs}) AS INET) BETWEEN %s AND %s', params + + class NetFamily(Transform): lookup_name = 'family' function = 'FAMILY' diff --git a/netbox/ipam/managers.py b/netbox/ipam/managers.py index 1ef00e125..8f1eb9ff1 100644 --- a/netbox/ipam/managers.py +++ b/netbox/ipam/managers.py @@ -1,10 +1,10 @@ from django.db.models import Manager from ipam.lookups import Host, Inet -from utilities.querysets import RestrictedQuerySet +from ipam.querysets import IPAddressQuerySet -class IPAddressManager(Manager.from_queryset(RestrictedQuerySet)): +class IPAddressManager(Manager.from_queryset(IPAddressQuerySet)): def get_queryset(self): """ diff --git a/netbox/ipam/migrations/0092_iprange_host_indexes.py b/netbox/ipam/migrations/0092_iprange_host_indexes.py new file mode 100644 index 000000000..76065bd27 --- /dev/null +++ b/netbox/ipam/migrations/0092_iprange_host_indexes.py @@ -0,0 +1,34 @@ +import django.db.models.functions.comparison +from django.db import migrations, models + +import ipam.fields +import ipam.lookups + + +class Migration(migrations.Migration): + dependencies = [ + ('ipam', '0091_alter_service_index_and_ordering'), + ] + + operations = [ + migrations.AddIndex( + model_name='iprange', + index=models.Index( + django.db.models.functions.comparison.Cast( + ipam.lookups.Host('start_address'), + output_field=ipam.fields.IPAddressField(), + ), + name='ipam_iprange_start_host', + ), + ), + migrations.AddIndex( + model_name='iprange', + index=models.Index( + django.db.models.functions.comparison.Cast( + ipam.lookups.Host('end_address'), + output_field=ipam.fields.IPAddressField(), + ), + name='ipam_iprange_end_host', + ), + ), + ] diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index cd4554257..51941526e 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -15,7 +15,7 @@ from ipam.constants import * from ipam.fields import IPAddressField, IPNetworkField from ipam.lookups import Host from ipam.managers import IPAddressManager -from ipam.querysets import PrefixQuerySet +from ipam.querysets import IPRangeQuerySet, PrefixQuerySet from ipam.validators import DNSValidator from netbox.config import get_config from netbox.models import OrganizationalModel, PrimaryModel @@ -425,14 +425,63 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary return Prefix.objects.filter(prefix__net_contained=str(self.prefix)) return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf) + @property + def usable_ip_bounds(self): + """ + Return the first and last IPs considered usable for available-IP calculations. + + Pools and IPv4 /31-/32 / IPv6 /127-/128 are fully usable; otherwise IPv4 excludes + network and broadcast, IPv6 excludes the subnet-router anycast address. + """ + network = netaddr.IPNetwork(self.prefix) + family = network.version + first = network.first + last = network.last + mask_length = network.prefixlen + + if ( + self.is_pool + or (family == 4 and mask_length >= 31) + or (family == 6 and mask_length >= 127) + ): + return ( + netaddr.IPAddress(first, version=family), + netaddr.IPAddress(last, version=family), + ) + + if family == 4: + return ( + netaddr.IPAddress(first + 1, version=family), + netaddr.IPAddress(last - 1, version=family), + ) + + return ( + netaddr.IPAddress(first + 1, version=family), + netaddr.IPAddress(last, version=family), + ) + + @property + def usable_size(self): + """ + The number of usable host addresses within the prefix (excludes reserved addresses). + """ + first_ip, last_ip = self.usable_ip_bounds + return int(last_ip) - int(first_ip) + 1 + def get_child_ranges(self, **kwargs): """ Return all IPRanges within this Prefix and VRF. """ + # A host BETWEEN over the prefix span uses the ipam_iprange_*_host btree indexes. + prefix = netaddr.IPNetwork(self.prefix) + bounds = ( + netaddr.IPAddress(prefix.first, version=prefix.version), + netaddr.IPAddress(prefix.last, version=prefix.version), + ) return IPRange.objects.filter( vrf=self.vrf, - start_address__net_host_contained=str(self.prefix), - end_address__net_host_contained=str(self.prefix), + start_address__host_between=bounds, + end_address__host_between=bounds, **kwargs ) @@ -441,52 +490,106 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return child IPAddresses belonging to any VRF. """ + # A host BETWEEN over the prefix span is index-sargable without the <<= containment recheck. + prefix = netaddr.IPNetwork(self.prefix) + bounds = ( + netaddr.IPAddress(prefix.first, version=prefix.version), + netaddr.IPAddress(prefix.last, version=prefix.version), + ) if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER: - return IPAddress.objects.filter(address__net_host_contained=str(self.prefix)) - return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf) + return IPAddress.objects.filter(address__host_between=bounds) + return IPAddress.objects.filter(address__host_between=bounds, vrf=self.vrf) def get_available_ips(self): """ Return all available IPs within this prefix as an IPSet. """ - prefix = netaddr.IPSet(self.prefix) - child_ips = netaddr.IPSet([ - ip.address.ip for ip in self.get_child_ips() - ]) - child_ranges = netaddr.IPSet([ - iprange.range for iprange in self.get_child_ranges().filter(mark_populated=True) - ]) - available_ips = prefix - child_ips - child_ranges + return netaddr.IPSet( + cidr + for start, end in self._available_intervals() + for cidr in netaddr.iprange_to_cidrs(start, end) + ) - # Pool, IPv4 /31-/32 or IPv6 /127-/128 sets are fully usable - if ( - self.is_pool - or (self.family == 4 and self.prefix.prefixlen >= 31) - or (self.family == 6 and self.prefix.prefixlen >= 127) - ): - return available_ips + def iter_available_ips(self): + """ + Yield the available IPs within this prefix as netaddr.IPAddress objects, in + ascending order. Unlike get_available_ips(), consumption is lazy: stopping + early stops reading from the database. + """ + for start, end in self._available_intervals(): + yield from netaddr.iter_iprange(start, end) - if self.family == 4: - # For "normal" IPv4 prefixes, omit first and last addresses - available_ips -= netaddr.IPSet([ - netaddr.IPAddress(self.prefix.first), - netaddr.IPAddress(self.prefix.last), - ]) - else: - # For IPv6 prefixes, omit the Subnet-Router anycast address - # per RFC 4291 - available_ips -= netaddr.IPSet([netaddr.IPAddress(self.prefix.first)]) + def get_available_ip_count(self): + """ + Return the number of available IPs within the prefix. + """ + first_ip, last_ip = self.usable_ip_bounds + usable_size = int(last_ip) - int(first_ip) + 1 - return available_ips + populated_intervals = self.get_child_ranges(mark_populated=True).get_intervals(first_ip, last_ip) + populated_count = sum(int(end) - int(start) + 1 for start, end in populated_intervals) + + # Populated ranges already cover the usable span; skip the child-IP count entirely. + if populated_count >= usable_size: + return 0 + + child_ip_count = ( + self.get_child_ips() + .filter(address__host_between=(first_ip, last_ip)) + .count_distinct_hosts(exclude_intervals=populated_intervals) + ) + + return max(usable_size - populated_count - child_ip_count, 0) + + def get_ip_usage_summary(self): + """ + Return the available IP count and utilization together as a dict, sharing a + single distinct-host scan. Intended for detail views rendering both values; + list views should call get_utilization() alone, which is cheaper per row. + """ + # Marked-utilized and container utilization need no host scan; delegate. + if self.mark_utilized or self.status == PrefixStatusChoices.STATUS_CONTAINER: + return { + 'available_ip_count': self.get_available_ip_count(), + 'utilization': self.get_utilization(), + } + + first_ip, last_ip = self.usable_ip_bounds + usable_size = int(last_ip) - int(first_ip) + 1 + + populated_intervals = self.get_child_ranges(mark_populated=True).get_intervals(first_ip, last_ip) + utilized_intervals = self.get_child_ranges(mark_utilized=True).get_intervals() + + counts = self.get_child_ips().count_distinct_hosts_pair( + bounds=(first_ip, last_ip), + bounded_exclude=populated_intervals, + total_exclude=utilized_intervals, + ) + + populated_count = sum(int(end) - int(start) + 1 for start, end in populated_intervals) + utilized_range_count = sum(int(end) - int(start) + 1 for start, end in utilized_intervals) + + prefix_size = self._get_utilization_denominator() + + return { + 'available_ip_count': max(usable_size - populated_count - counts['bounded'], 0), + 'utilization': min(float(utilized_range_count + counts['total']) / prefix_size * 100, 100), + } def get_first_available_ip(self): """ Return the first available IP within the prefix (or None). """ - available_ips = self.get_available_ips() - if not available_ips: + first_ip, last_ip = self.usable_ip_bounds + populated_intervals = self.get_child_ranges(mark_populated=True).get_intervals(first_ip, last_ip) + + first_available_ip = self.get_child_ips().first_available_host( + first_ip, last_ip, exclude_intervals=populated_intervals, + ) + + if first_available_ip is None: return None - return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen) + return f'{first_available_ip}/{self.prefix.prefixlen}' def get_utilization(self): """ @@ -504,20 +607,43 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary child_prefixes = netaddr.IPSet([p.prefix for p in queryset]) utilization = float(child_prefixes.size) / self.prefix.size * 100 else: - # Compile an IPSet to avoid counting duplicate IPs - child_ips = netaddr.IPSet() - for iprange in self.get_child_ranges().filter(mark_utilized=True): - child_ips.add(iprange.range) - for ip in self.get_child_ips(): - child_ips.add(ip.address.ip) + prefix_size = self._get_utilization_denominator() + utilized_intervals = self.get_child_ranges(mark_utilized=True).get_intervals() + utilized_range_count = sum(int(end) - int(start) + 1 for start, end in utilized_intervals) - prefix_size = self.prefix.size - if self.prefix.version == 4 and self.prefix.prefixlen < 31 and not self.is_pool: - prefix_size -= 2 - utilization = float(child_ips.size) / prefix_size * 100 + # Utilized ranges already saturate the prefix; skip the child-IP count. + if utilized_range_count >= prefix_size: + return 100 + + child_ip_count = self.get_child_ips().count_distinct_hosts( + exclude_intervals=utilized_intervals, + ) + + utilization = float(utilized_range_count + child_ip_count) / prefix_size * 100 return min(utilization, 100) + def _available_intervals(self): + """ + Yield the available (start, end) host intervals within the prefix. + """ + first_ip, last_ip = self.usable_ip_bounds + populated_intervals = self.get_child_ranges(mark_populated=True).get_intervals(first_ip, last_ip) + + return self.get_child_ips().available_intervals( + first_ip, last_ip, exclude_intervals=populated_intervals, + ) + + def _get_utilization_denominator(self): + """ + The address count utilization is measured against (IPv4 non-pool prefixes + exclude the network and broadcast addresses; IPv6 uses the full prefix size). + """ + prefix_size = self.prefix.size + if self.prefix.version == 4 and self.prefix.prefixlen < 31 and not self.is_pool: + return prefix_size - 2 + return prefix_size + class IPRange(ContactsMixin, PrimaryModel): """ @@ -576,12 +702,24 @@ class IPRange(ContactsMixin, PrimaryModel): help_text=_("Report space as fully utilized") ) + objects = IPRangeQuerySet.as_manager() + clone_fields = ( 'vrf', 'tenant', 'status', 'role', 'description', 'mark_populated', 'mark_utilized', ) class Meta: ordering = (F('vrf').asc(nulls_first=True), 'start_address', 'pk') # (vrf, start_address) may be non-unique + indexes = ( + models.Index( + Cast(Host('start_address'), output_field=IPAddressField()), + name='ipam_iprange_start_host', + ), + models.Index( + Cast(Host('end_address'), output_field=IPAddressField()), + name='ipam_iprange_end_host', + ), + ) verbose_name = _('IP range') verbose_name_plural = _('IP ranges') @@ -709,40 +847,14 @@ class IPRange(ContactsMixin, PrimaryModel): def get_status_color(self): return IPRangeStatusChoices.colors.get(self.status) - def get_child_ips(self): - """ - Return all IPAddresses within this IPRange and VRF. - """ - return IPAddress.objects.filter( - address__gte=self.start_address, - address__lte=self.end_address, - vrf=self.vrf - ) - - def get_available_ips(self): - """ - Return all available IPs within this range as an IPSet. - """ - if self.mark_populated: - return netaddr.IPSet() - - range = netaddr.IPRange(self.start_address.ip, self.end_address.ip) - child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()]) - - return netaddr.IPSet(range) - child_ips - @cached_property def first_available_ip(self): """ Return the first available IP within the range (or None). """ - available_ips = self.get_available_ips() - if not available_ips: - return None + return self.get_first_available_ip() - return '{}/{}'.format(next(available_ips.__iter__()), self.start_address.prefixlen) - - @cached_property + @property def utilization(self): """ Determine the utilization of the range and return it as a percentage. @@ -750,12 +862,79 @@ class IPRange(ContactsMixin, PrimaryModel): if self.mark_utilized: return 100 - # Compile an IPSet to avoid counting duplicate IPs - child_count = netaddr.IPSet([ - ip.address.ip for ip in self.get_child_ips() - ]).size + return min(float(self._occupied_host_count) / self.size * 100, 100) - return min(float(child_count) / self.size * 100, 100) + def get_child_ips(self): + """ + Return all IPAddresses within this IPRange and VRF. + """ + return IPAddress.objects.filter( + vrf=self.vrf, + address__host_between=(self.start_address.ip, self.end_address.ip), + ) + + def get_available_ips(self): + """ + Return all available IPs within this range as an IPSet. + """ + return netaddr.IPSet( + cidr + for start, end in self._available_intervals() + for cidr in netaddr.iprange_to_cidrs(start, end) + ) + + def iter_available_ips(self): + """ + Yield the available IPs within this range as netaddr.IPAddress objects, in + ascending order. Unlike get_available_ips(), consumption is lazy: stopping + early stops reading from the database. + """ + for start, end in self._available_intervals(): + yield from netaddr.iter_iprange(start, end) + + def get_available_ip_count(self): + """ + Return the number of available IPs within the range. + """ + if self.mark_populated: + return 0 + + return max(self.size - self._occupied_host_count, 0) + + def get_first_available_ip(self): + """ + Return the first available IP within the range (or None). + """ + if self.mark_populated: + return None + + first_available_ip = self.get_child_ips().first_available_host( + self.start_address.ip, self.end_address.ip, + ) + + if first_available_ip is None: + return None + + return f'{first_available_ip}/{self.start_address.prefixlen}' + + def _available_intervals(self): + """ + Yield the available (start, end) host intervals within the range. + """ + if self.mark_populated: + return iter(()) + + return self.get_child_ips().available_intervals( + self.start_address.ip, self.end_address.ip, + ) + + @cached_property + def _occupied_host_count(self): + """ + The number of distinct occupied hosts within the range, cached for the + lifetime of the instance. + """ + return self.get_child_ips().count_distinct_hosts() class IPAddress(ContactsMixin, PrimaryModel): @@ -948,10 +1127,10 @@ class IPAddress(ContactsMixin, PrimaryModel): # Disallow the creation of IPAddresses within an IPRange with mark_populated=True parent_range_qs = IPRange.objects.filter( - start_address__lte=self.address, - end_address__gte=self.address, + start_address__host__inet__lte=self.address.ip, + end_address__host__inet__gte=self.address.ip, vrf=self.vrf, - mark_populated=True + mark_populated=True, ) if not self.pk and (parent_range := parent_range_qs.first()): raise ValidationError({ diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py index 12bd8328c..4a48ee81d 100644 --- a/netbox/ipam/querysets.py +++ b/netbox/ipam/querysets.py @@ -1,18 +1,51 @@ +import heapq + +import netaddr from django.contrib.contenttypes.models import ContentType from django.db.models import Count, F, OuterRef, Q, Subquery, Value from django.db.models.expressions import RawSQL -from django.db.models.functions import NullIf, Round +from django.db.models.functions import Cast, NullIf, Round from utilities.query import count_related from utilities.querysets import RestrictedQuerySet +from .fields import IPAddressField +from .lookups import Host + __all__ = ( 'ASNRangeQuerySet', + 'IPAddressQuerySet', + 'IPRangeQuerySet', 'PrefixQuerySet', 'VLANGroupQuerySet', 'VLANQuerySet', ) +# The host portion of an IP address (mask ignored), in the same form as the +# ipam_ipaddress_host expression index. +HOST_ADDRESS = Cast(Host('address'), output_field=IPAddressField()) + + +def _merge_intervals(intervals): + """ + Return the union of (start, end) netaddr.IPAddress intervals, merged and sorted. + """ + if not intervals: + return [] + + intervals = sorted(intervals) + merged = [intervals[0]] + + for start, end in intervals[1:]: + current_start, current_end = merged[-1] + # Adjacency math in int space; netaddr raises at the address-space maximum. + if start.version == current_end.version and int(start) <= int(current_end) + 1: + merged[-1] = (current_start, max(current_end, end)) + else: + merged.append((start, end)) + + return merged + class ASNRangeQuerySet(RestrictedQuerySet): @@ -32,6 +65,162 @@ class ASNRangeQuerySet(RestrictedQuerySet): return self.annotate(asn_count=Subquery(asns)) +class IPAddressQuerySet(RestrictedQuerySet): + + def count_distinct_hosts(self, exclude_intervals=()): + """ + Count distinct host addresses, optionally excluding (start, end) netaddr.IPAddress intervals. + """ + queryset = self + for start, end in exclude_intervals: + queryset = queryset.exclude(address__host_between=(start, end)) + + return queryset.aggregate(count=Count(HOST_ADDRESS, distinct=True))['count'] + + def count_distinct_hosts_pair(self, bounds, bounded_exclude=(), total_exclude=()): + """ + Return two distinct host counts computed in a single scan, as a dict: + 'bounded' counts hosts within the (first_ip, last_ip) bounds excluding the + bounded_exclude intervals; 'total' counts all hosts excluding the + total_exclude intervals. Interval arguments match the output of + IPRangeQuerySet.get_intervals(). Avoids a second scan of the host expression + index when both counts are needed. Use only when both counts are needed (e.g. + Prefix.get_ip_usage_summary()); single-purpose callers should prefer + count_distinct_hosts(). + """ + # The deduplicated column is already a bare host; plain comparisons beat + # the host_between lookup here, which would re-wrap it in HOST()::inet. + bounded_q = Q(host_address__range=(str(bounds[0]), str(bounds[1]))) + for start, end in bounded_exclude: + bounded_q &= ~Q(host_address__range=(str(start), str(end))) + total_q = Q() + for start, end in total_exclude: + total_q &= ~Q(host_address__range=(str(start), str(end))) + + hosts = self.order_by().annotate(host_address=HOST_ADDRESS).values('host_address').distinct() + return hosts.aggregate( + bounded=Count('host_address', filter=bounded_q), + # An empty Q is falsy; fall back to a plain count of all hosts. + total=Count('host_address', filter=total_q or None), + ) + + def _iter_distinct_hosts(self, first_ip, last_ip, batch_size): + """ + Yield the distinct occupied hosts in [first_ip, last_ip] in ascending order, + fetched in LIMIT batches that resume just past the last seen host. (A + server-side cursor is unsuitable here: on autocommit connections Django + declares it WITH HOLD, which materializes the full result at DECLARE.) + """ + resume = first_ip + while True: + # order_by() first clears the default ordering, which would otherwise + # leak into SELECT and break distinct(). + hosts = list( + self.filter(address__host_between=(resume, last_ip)) + .order_by() + .annotate(host_address=HOST_ADDRESS) + .values_list('host_address', flat=True) + .distinct() + .order_by('host_address')[:batch_size] + ) + for host in hosts: + yield host.ip + if len(hosts) < batch_size: + return + last_host = hosts[-1].ip + if int(last_host) >= int(last_ip): + return + resume = netaddr.IPAddress(int(last_host) + 1, version=last_host.version) + + def available_intervals(self, first_ip, last_ip, exclude_intervals=(), batch_size=5000): + """ + Yield the unoccupied (start, end) netaddr.IPAddress intervals (inclusive) + within [first_ip, last_ip], in ascending order. exclude_intervals are + (start, end) netaddr.IPAddress pairs; they are merged and sorted internally, + intervals of a foreign address family are ignored, and addresses they cover + count as occupied. Consumption is lazy: a caller that stops early stops + fetching host batches. + """ + if batch_size < 1: + raise ValueError('batch_size must be greater than zero') + + first_int, last_int = int(first_ip), int(last_ip) + version = first_ip.version + + if first_int > last_int: + return + # Normalize: the sweep below requires sorted, non-overlapping, same-family intervals. + exclude_intervals = _merge_intervals([ + (start, end) + for start, end in exclude_intervals + if start.version == end.version == version + ]) + intervals = [(int(start), int(end)) for start, end in exclude_intervals] + + # Fast path: one merged excluded interval covers the entire span. + if intervals and intervals[0][0] <= first_int and intervals[0][1] >= last_int: + return + + hosts = ( + (int(host), int(host)) + for host in self._iter_distinct_hosts(first_ip, last_ip, batch_size) + ) + + candidate = first_int + # Ties on `start` are harmless; the sweep handles overlapping intervals. + for start, end in heapq.merge(intervals, hosts): + if end < candidate: + continue + if start > candidate: + yield ( + netaddr.IPAddress(candidate, version=version), + netaddr.IPAddress(min(start - 1, last_int), version=version), + ) + candidate = max(candidate, end + 1) + if candidate > last_int: + return + + if candidate <= last_int: + yield ( + netaddr.IPAddress(candidate, version=version), + netaddr.IPAddress(last_int, version=version), + ) + + def first_available_host(self, first_ip, last_ip, exclude_intervals=()): + """ + Return the first host in [first_ip, last_ip] neither present nor in an excluded interval (or None). + """ + interval = next(self.available_intervals(first_ip, last_ip, exclude_intervals), None) + return interval[0] if interval else None + + +class IPRangeQuerySet(RestrictedQuerySet): + + def get_intervals(self, first_ip=None, last_ip=None): + """ + Return ranges as merged (start, end) netaddr.IPAddress intervals, optionally clipped to the bounds. + """ + intervals = [] + + # order_by() clears the default ordering; _merge_intervals() sorts anyway. + for start_address, end_address in self.order_by().values_list('start_address', 'end_address'): + start, end = start_address.ip, end_address.ip + + if first_ip is not None: + if end < first_ip: + continue + start = max(start, first_ip) + + if last_ip is not None: + if start > last_ip: + continue + end = min(end, last_ip) + + intervals.append((start, end)) + + return _merge_intervals(intervals) + + class PrefixQuerySet(RestrictedQuerySet): def annotate_hierarchy(self): diff --git a/netbox/ipam/tests/test_fields.py b/netbox/ipam/tests/test_fields.py new file mode 100644 index 000000000..b7bcd3b28 --- /dev/null +++ b/netbox/ipam/tests/test_fields.py @@ -0,0 +1,29 @@ +from django.test import TestCase +from netaddr import IPAddress + +from ipam.fields import IPAddressField, IPNetworkField + + +class BaseIPFieldTestCase(TestCase): + """ + Regression coverage for BaseIPField.get_prep_value() — zero addresses such as + 0.0.0.0 and :: are valid hosts and must not be treated as empty values. + """ + + def test_get_prep_value_accepts_ipv4_zero_address(self): + # Regression: 0.0.0.0 is a valid host, not an empty value. + self.assertEqual(IPAddressField().get_prep_value(IPAddress('0.0.0.0')), '0.0.0.0') + + def test_get_prep_value_accepts_ipv6_zero_address(self): + # Regression: :: is a valid host, not an empty value. + self.assertEqual(IPAddressField().get_prep_value(IPAddress('::')), '::') + + def test_get_prep_value_passes_through_empty(self): + self.assertIsNone(IPNetworkField().get_prep_value(None)) + self.assertIsNone(IPAddressField().get_prep_value('')) + + def test_get_prep_value_preserves_raw_zero_as_empty(self): + # Raw int 0 is preserved as the legacy "empty" sentinel; Django's ORM never + # passes it directly, but the previous `not value` check returned None for it. + self.assertIsNone(IPAddressField().get_prep_value(0)) + self.assertIsNone(IPNetworkField().get_prep_value(0)) diff --git a/netbox/ipam/tests/test_lookups.py b/netbox/ipam/tests/test_lookups.py index c23c910cf..2280073ac 100644 --- a/netbox/ipam/tests/test_lookups.py +++ b/netbox/ipam/tests/test_lookups.py @@ -1,7 +1,9 @@ +import netaddr from django.db.backends.postgresql.psycopg_any import NumericRange from django.test import TestCase +from netaddr import IPNetwork -from ipam.models import VLANGroup +from ipam.models import IPAddress, VLANGroup class VLANGroupRangeContainsLookupTestCase(TestCase): @@ -65,3 +67,134 @@ class VLANGroupRangeContainsLookupTestCase(TestCase): specific condition. """ self.assertFalse(VLANGroup.objects.filter(pk=self.g_empty.pk, vid_ranges__range_contains=1).exists()) + + +class IPAddressHostBetweenLookupTestCase(TestCase): + @classmethod + def setUpTestData(cls): + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.0/24')), + IPAddress(address=IPNetwork('192.0.2.1/24')), + IPAddress(address=IPNetwork('192.0.2.5/32')), + IPAddress(address=IPNetwork('192.0.2.10/25')), + IPAddress(address=IPNetwork('192.0.2.11/24')), + IPAddress(address=IPNetwork('2001:db8::1/64')), + IPAddress(address=IPNetwork('2001:db8::5/128')), + IPAddress(address=IPNetwork('2001:db8::10/64')), + )) + + def test_ipv4_boundaries_inclusive(self): + """ + Tests that both bounds are included and hosts outside the window are excluded. + """ + queryset = IPAddress.objects.filter( + address__host_between=(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10')) + ) + self.assertEqual( + sorted(str(ip.address) for ip in queryset), + ['192.0.2.1/24', '192.0.2.10/25', '192.0.2.5/32'], + ) + + def test_mask_insensitive(self): + """ + Tests that hosts match regardless of their mask length. + """ + queryset = IPAddress.objects.filter( + address__host_between=(netaddr.IPAddress('192.0.2.5'), netaddr.IPAddress('192.0.2.5')) + ) + self.assertEqual(queryset.count(), 1) + + def test_ipv6(self): + """ + Tests that IPv6 hosts filter by host portion. + """ + queryset = IPAddress.objects.filter( + address__host_between=(netaddr.IPAddress('2001:db8::1'), netaddr.IPAddress('2001:db8::5')) + ) + self.assertEqual(queryset.count(), 2) + + def test_bounds_mask_stripped(self): + """ + Tests that bounds supplied with a mask compare by host portion only. + """ + queryset = IPAddress.objects.filter( + address__host_between=(IPNetwork('192.0.2.1/24'), IPNetwork('192.0.2.10/24')) + ) + self.assertEqual(queryset.count(), 3) + + def test_invalid_bounds_raise(self): + """ + Tests that a bounds value which is not a two-item pair raises ValueError. + """ + with self.assertRaises(ValueError): + IPAddress.objects.filter(address__host_between=(netaddr.IPAddress('192.0.2.1'),)) + + def test_invalid_bound_value_raises(self): + """ + Tests that a bound which is not a valid IP address raises ValueError. + """ + with self.assertRaises(ValueError): + IPAddress.objects.filter(address__host_between=('invalid', netaddr.IPAddress('192.0.2.10'))) + + def test_mixed_family_bounds_raise(self): + """ + Tests that bounds from different address families raise ValueError. + """ + with self.assertRaises(ValueError): + IPAddress.objects.filter( + address__host_between=(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('2001:db8::1')) + ) + + def test_sql_uses_cast_host_expression(self): + """ + Tests that the compiled SQL matches the ipam_ipaddress_host index expression. + """ + queryset = IPAddress.objects.filter( + address__host_between=(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10')) + ) + self.assertIn('CAST(HOST(', str(queryset.query)) + + +class IPAddressNetLookupsTestCase(TestCase): + @classmethod + def setUpTestData(cls): + IPAddress.objects.bulk_create(( + IPAddress(address='10.0.0.1/24'), + IPAddress(address='10.0.0.2/24'), + IPAddress(address='10.0.0.1/25'), # Same host as the first, different mask + IPAddress(address='2001:db8::1/64'), + )) + + def test_net_host_matches_host_ignoring_mask(self): + """net_host matches every address whose host portion equals the value.""" + qs = IPAddress.objects.filter(address__net_host='10.0.0.1') + self.assertEqual(qs.count(), 2) + + def test_net_host_predicate_is_inet_typed(self): + """net_host casts the host expression to inet so the inet host index applies.""" + sql = str(IPAddress.objects.filter(address__net_host='10.0.0.1').query) + self.assertIn('CAST(HOST(', sql) + self.assertIn('AS INET) =', sql) + + def test_net_in_without_mask(self): + """net_in matches host values supplied without a mask.""" + qs = IPAddress.objects.filter(address__net_in=['10.0.0.1', '10.0.0.2']) + self.assertEqual(qs.count(), 3) + + def test_net_in_with_mask(self): + """net_in matches an exact address/mask value.""" + qs = IPAddress.objects.filter(address__net_in=['10.0.0.1/25']) + self.assertEqual(qs.count(), 1) + + def test_net_in_normalizes_ipv6(self): + """net_in matches an expanded IPv6 form against the canonical host value.""" + qs = IPAddress.objects.filter( + address__net_in=['2001:0db8:0000:0000:0000:0000:0000:0001'] + ) + self.assertEqual(qs.count(), 1) + + def test_net_in_predicate_is_inet_typed(self): + """net_in casts the host expression to inet so the inet host index applies.""" + sql = str(IPAddress.objects.filter(address__net_in=['10.0.0.1']).query) + self.assertIn('CAST(HOST(', sql) + self.assertIn('AS INET) IN', sql) diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index becbf9413..b9a99a445 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -1,3 +1,4 @@ +import netaddr from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db.backends.postgresql.psycopg_any import NumericRange @@ -8,6 +9,7 @@ from dcim.models import Site, SiteGroup from ipam.choices import * from ipam.constants import SERVICE_PORT_MAX, SERVICE_PORT_MIN from ipam.models import * +from ipam.utils import rebuild_prefixes from utilities.data import string_to_ranges from virtualization.models import VirtualMachine @@ -115,7 +117,7 @@ class IPRangeTestCase(TestCase): self.assertEqual(iprange.size, 1) self.assertEqual(str(iprange), '192.0.2.10-192.0.2.10/24') - self.assertEqual(iprange.first_available_ip, '192.0.2.10/24') + self.assertEqual(iprange.get_first_available_ip(), '192.0.2.10/24') def test_first_available_ip_consumed_single_address_range(self): iprange = IPRange.objects.create( @@ -125,7 +127,7 @@ class IPRangeTestCase(TestCase): IPAddress.objects.create(address=IPNetwork('192.0.2.10/24')) # The sole address in the range is now assigned, so no IPs remain available. - self.assertIsNone(iprange.first_available_ip) + self.assertIsNone(iprange.get_first_available_ip()) def test_single_address_range_ipv6(self): # IPRange.name has IPv4/IPv6-specific formatting; exercise the IPv6 branch @@ -140,7 +142,7 @@ class IPRangeTestCase(TestCase): self.assertEqual(iprange.size, 1) self.assertEqual(str(iprange), '2001:db8::10-2001:db8::10/64') - self.assertEqual(iprange.first_available_ip, '2001:db8::10/64') + self.assertEqual(iprange.get_first_available_ip(), '2001:db8::10/64') def test_reversed_range(self): iprange = IPRange( @@ -167,9 +169,207 @@ class IPRangeTestCase(TestCase): with self.assertRaisesMessage(ValidationError, 'Defined addresses overlap'): iprange.clean() + def test_get_child_ips_host_portion(self): + iprange = IPRange.objects.create( + start_address=IPNetwork('10.0.0.2/24'), + end_address=IPNetwork('10.0.0.254/24'), + ) + + ip1 = IPAddress.objects.create(address=IPNetwork('10.0.0.2/32')) + ip2 = IPAddress.objects.create(address=IPNetwork('10.0.0.3/24')) + + self.assertEqual(set(iprange.get_child_ips()), {ip1, ip2}) + + def test_get_available_ips(self): + """ + Tests that occupied hosts are deduplicated and excluded from the available set. + """ + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.13/24'), + ) + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.10/24')), + IPAddress(address=IPNetwork('192.0.2.10/32')), + )) + + self.assertEqual(iprange.get_available_ips(), IPSet(['192.0.2.11/32', '192.0.2.12/31'])) + + def test_get_available_ips_mark_populated(self): + """ + Tests that a populated range reports no available IPs. + """ + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.13/24'), + mark_populated=True, + ) + + self.assertEqual(iprange.get_available_ips(), IPSet()) + + def test_get_available_ips_vrf(self): + """ + Tests that IPs in other VRFs do not consume range space. + """ + vrf1 = VRF.objects.create(name='VRF 1') + vrf2 = VRF.objects.create(name='VRF 2') + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.11/24'), + vrf=vrf1, + ) + IPAddress.objects.create(address=IPNetwork('192.0.2.10/24'), vrf=vrf2) + + self.assertEqual(iprange.get_available_ips(), IPSet(['192.0.2.10/31'])) + + def test_iter_available_ips(self): + """ + Tests that iter_available_ips() yields the same addresses as get_available_ips() in order. + """ + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.13/24'), + ) + IPAddress.objects.create(address=IPNetwork('192.0.2.11/24')) + + self.assertEqual(list(iprange.iter_available_ips()), sorted(iprange.get_available_ips())) + + def test_available_ip_count(self): + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.12/24')) + + self.assertEqual(iprange.get_available_ip_count(), 9) + + def test_available_ip_count_distinct_hosts(self): + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + ) + + # Two rows for .10 (different masks) must dedupe to a single occupied host. + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.10/24')), + IPAddress(address=IPNetwork('192.0.2.10/32')), + IPAddress(address=IPNetwork('192.0.2.11/24')), + )) + + self.assertEqual(iprange.get_available_ip_count(), 8) + + def test_available_ip_count_vrf(self): + vrf1 = VRF.objects.create(name='VRF 1') + vrf2 = VRF.objects.create(name='VRF 2') + + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + vrf=vrf1, + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.12/24'), vrf=vrf1) + IPAddress.objects.create(address=IPNetwork('192.0.2.13/24'), vrf=vrf2) + + # Only the VRF 1 IP should count. + self.assertEqual(iprange.get_available_ip_count(), 9) + + def test_available_ip_count_populated(self): + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + mark_populated=True, + ) + + self.assertEqual(iprange.get_available_ip_count(), 0) + + def test_first_available_ip_full(self): + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.11/24'), + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.10/24')) + IPAddress.objects.create(address=IPNetwork('192.0.2.11/24')) + + self.assertIsNone(iprange.get_first_available_ip()) + + def test_first_available_ip_populated(self): + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + mark_populated=True, + ) + + self.assertIsNone(iprange.get_first_available_ip()) + + def test_first_available_ip_ipv6(self): + iprange = IPRange.objects.create( + start_address=IPNetwork('::/126'), + end_address=IPNetwork('::3/126'), + ) + + self.assertEqual(iprange.get_first_available_ip(), '::/126') + + def test_utilization_distinct_hosts(self): + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + ) + + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.10/24')), + IPAddress(address=IPNetwork('192.0.2.10/32')), + IPAddress(address=IPNetwork('192.0.2.11/24')), + )) + + # Two distinct hosts in a 10-address range. + self.assertEqual(iprange.utilization, 2 / 10 * 100) + + def test_utilization_vrf(self): + vrf1 = VRF.objects.create(name='VRF 1') + vrf2 = VRF.objects.create(name='VRF 2') + + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + vrf=vrf1, + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.12/24'), vrf=vrf1) + IPAddress.objects.create(address=IPNetwork('192.0.2.13/24'), vrf=vrf2) + + # Only the VRF 1 IP counts toward utilization. + self.assertEqual(iprange.utilization, 1 / 10 * 100) + + def test_utilization_duplicate_ips_vrf(self): + """ + Tests that identical IPs in a non-unique VRF count once toward range utilization. + """ + vrf = VRF.objects.create(name='VRF 1', enforce_unique=False) + iprange = IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + vrf=vrf, + ) + + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.12/24'), vrf=vrf), + IPAddress(address=IPNetwork('192.0.2.12/24'), vrf=vrf), + )) + + self.assertEqual(iprange.utilization, 1 / 10 * 100) + class PrefixTestCase(TestCase): + def assertAvailableIPCountMatchesIPSet(self, prefix): + """ + Confirm that get_available_ip_count() matches get_available_ips().size for the supplied prefix. + """ + self.assertEqual(prefix.get_available_ip_count(), prefix.get_available_ips().size) + def test_family_string(self): # Test property when prefix is a string prefix = Prefix(prefix='10.0.0.0/8') @@ -253,6 +453,23 @@ class PrefixTestCase(TestCase): self.assertEqual(child_ranges[0], ranges[2]) self.assertEqual(child_ranges[1], ranges[3]) + def test_get_child_ranges_other_family(self): + """ + Tests that ranges of a different address family are not returned. + """ + prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.16/28')) + IPRange.objects.bulk_create(( + IPRange( + start_address=IPNetwork('192.168.0.18/28'), end_address=IPNetwork('192.168.0.20/28'), size=3 + ), + IPRange(start_address=IPNetwork('::1/64'), end_address=IPNetwork('::2/64'), size=2), + )) + + child_ranges = prefix.get_child_ranges() + + self.assertEqual(len(child_ranges), 1) + self.assertEqual(child_ranges[0].start_address, IPNetwork('192.168.0.18/28')) + def test_get_child_ips(self): vrfs = VRF.objects.bulk_create(( VRF(name='VRF 1'), @@ -332,6 +549,319 @@ class PrefixTestCase(TestCase): self.assertEqual(available_ips, missing_ips) + def test_iter_available_ips(self): + """ + Tests that iter_available_ips() yields the same addresses as get_available_ips() in order. + """ + parent_prefix = Prefix.objects.create(prefix=IPNetwork('10.0.0.0/28')) + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('10.0.0.1/28')), + IPAddress(address=IPNetwork('10.0.0.5/28')), + )) + IPRange.objects.create( + start_address=IPNetwork('10.0.0.8/28'), + end_address=IPNetwork('10.0.0.9/28'), + mark_populated=True, + ) + + available_ips = list(parent_prefix.iter_available_ips()) + + self.assertEqual(available_ips, sorted(parent_prefix.get_available_ips())) + self.assertEqual(available_ips[0], netaddr.IPAddress('10.0.0.2')) + self.assertEqual(available_ips[-1], netaddr.IPAddress('10.0.0.14')) + + def test_get_available_ips_ipv6(self): + """ + Tests that the subnet-router anycast address is excluded and the last address included. + """ + parent_prefix = Prefix.objects.create(prefix=IPNetwork('2001:db8::/126')) + IPAddress.objects.create(address=IPNetwork('2001:db8::1/126')) + + self.assertEqual(parent_prefix.get_available_ips(), IPSet(['2001:db8::2/127'])) + + def test_get_available_ips_pool(self): + """ + Tests that pool prefixes include the network and broadcast addresses. + """ + parent_prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/30'), is_pool=True) + IPAddress.objects.create(address=IPNetwork('192.0.2.1/30')) + + self.assertEqual(parent_prefix.get_available_ips(), IPSet(['192.0.2.0/32', '192.0.2.2/31'])) + + def test_available_ip_count_distinct_hosts(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/29'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.1/29')), + IPAddress(address=IPNetwork('192.0.2.1/32')), + IPAddress(address=IPNetwork('192.0.2.3/29')), + )) + + # Usable hosts in /29: 6. Two unique hosts occupy .1 and .3. + self.assertEqual(prefix.get_available_ip_count(), 4) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_populated_ranges(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/29'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.1/29')), + IPAddress(address=IPNetwork('192.0.2.3/29')), # Inside the populated range; not double-counted. + )) + + IPRange.objects.create( + start_address=IPNetwork('192.0.2.3/29'), + end_address=IPNetwork('192.0.2.4/29'), + mark_populated=True, + ) + + # Usable 6, one IP outside the range at .1, populated range covers .3-.4. + # Available: .2, .5, .6. + self.assertEqual(prefix.get_available_ip_count(), 3) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_ipv4_pool(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/30'), + status=PrefixStatusChoices.STATUS_ACTIVE, + is_pool=True, + ) + + self.assertEqual(prefix.get_available_ip_count(), 4) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_ipv4_non_pool(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/30'), + status=PrefixStatusChoices.STATUS_ACTIVE, + is_pool=False, + ) + + self.assertEqual(prefix.get_available_ip_count(), 2) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_ipv4_non_pool_ignores_unusable_ips(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/30'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + # Network and broadcast addresses are unusable for non-pool IPv4 prefixes; + # an IP assigned to either must not reduce the available count. + IPAddress.objects.create(address=IPNetwork('192.0.2.0/30')) + IPAddress.objects.create(address=IPNetwork('192.0.2.3/30')) + + self.assertEqual(prefix.get_available_ip_count(), 2) + self.assertEqual(prefix.get_first_available_ip(), '192.0.2.1/30') + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_ipv6(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('2001:db8::/126'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + # /126 has 4 addresses; normal IPv6 prefix excludes the first. + self.assertEqual(prefix.get_available_ip_count(), 3) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_ipv6_ignores_subnet_router_anycast(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('2001:db8::/126'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + # The subnet-router anycast (::) address is unusable for normal IPv6 prefixes; + # an IP assigned there must not reduce the available count. + IPAddress.objects.create(address=IPNetwork('2001:db8::/126')) + + self.assertEqual(prefix.get_available_ip_count(), 3) + self.assertEqual(prefix.get_first_available_ip(), '2001:db8::1/126') + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_ipv6_127(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('2001:db8::/127'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + self.assertEqual(prefix.get_available_ip_count(), 2) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_ipv6_populated_range(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('2001:db8::/126'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPRange.objects.create( + start_address=IPNetwork('2001:db8::1/126'), + end_address=IPNetwork('2001:db8::2/126'), + mark_populated=True, + ) + + # Usable IPv6 hosts in /126: ::1, ::2, ::3. Populated: ::1-::2. + self.assertEqual(prefix.get_available_ip_count(), 1) + self.assertEqual(prefix.get_first_available_ip(), '2001:db8::3/126') + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_overlapping_ranges(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/29'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPRange.objects.create( + start_address=IPNetwork('192.0.2.1/29'), + end_address=IPNetwork('192.0.2.3/29'), + mark_populated=True, + ) + IPRange.objects.create( + start_address=IPNetwork('192.0.2.2/29'), + end_address=IPNetwork('192.0.2.4/29'), + mark_populated=True, + ) + + # Usable hosts: .1-.6 => 6. Populated union: .1-.4 => 4. Available: .5-.6 => 2. + self.assertEqual(prefix.get_available_ip_count(), 2) + self.assertEqual(prefix.get_first_available_ip(), '192.0.2.5/29') + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_vrf(self): + vrf1 = VRF.objects.create(name='VRF 1') + vrf2 = VRF.objects.create(name='VRF 2') + + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/29'), + vrf=vrf1, + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.1/29'), vrf=vrf1) + IPAddress.objects.create(address=IPNetwork('192.0.2.2/29'), vrf=vrf2) + + # Usable .1-.6 => 6. Only the VRF 1 IP should count. + self.assertEqual(prefix.get_available_ip_count(), 5) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_vrf_ranges(self): + vrf1 = VRF.objects.create(name='VRF 1') + vrf2 = VRF.objects.create(name='VRF 2') + + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/29'), + vrf=vrf1, + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + # Covers every usable host, but in a different VRF. + IPRange.objects.create( + start_address=IPNetwork('192.0.2.1/29'), + end_address=IPNetwork('192.0.2.6/29'), + vrf=vrf2, + mark_populated=True, + ) + + self.assertEqual(prefix.get_available_ip_count(), 6) + self.assertEqual(prefix.get_first_available_ip(), '192.0.2.1/29') + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_fully_populated(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/30'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + # Populated range covers every usable address (.1-.2 in a non-pool /30). + IPRange.objects.create( + start_address=IPNetwork('192.0.2.1/30'), + end_address=IPNetwork('192.0.2.2/30'), + mark_populated=True, + ) + + # Exercises the early-return paths that skip the child-IP count and + # the host-stream iterator entirely. + self.assertEqual(prefix.get_available_ip_count(), 0) + self.assertIsNone(prefix.get_first_available_ip()) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_query_count(self): + """ + Tests that the count runs one interval query plus exactly one host scan. + """ + prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24')) + + IPAddress.objects.bulk_create( + IPAddress(address=IPNetwork(f'192.0.2.{i}/24')) for i in range(1, 11) + ) + + IPRange.objects.create( + start_address=IPNetwork('192.0.2.20/24'), + end_address=IPNetwork('192.0.2.29/24'), + mark_populated=True, + ) + + with self.assertNumQueries(2): + prefix.get_available_ip_count() + + def test_available_ip_count_container(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_CONTAINER, + ) + + # A child prefix exists but does not reduce the available IP count. + Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/26'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + self.assertEqual(prefix.get_available_ip_count(), 254) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_container_vrf_duplicate_hosts(self): + vrf1 = VRF.objects.create(name='VRF 1') + vrf2 = VRF.objects.create(name='VRF 2') + + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_CONTAINER, + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), vrf=vrf1) + IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), vrf=vrf2) + IPAddress.objects.create(address=IPNetwork('192.0.2.2/24'), vrf=vrf2) + + # A global container counts child IPs from all VRFs; the duplicate host + # counts once. 254 usable - 2 distinct hosts. + self.assertEqual(prefix.get_available_ip_count(), 252) + self.assertAvailableIPCountMatchesIPSet(prefix) + + def test_available_ip_count_container_vrf_ip_in_populated_range(self): + vrf1 = VRF.objects.create(name='VRF 1') + + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_CONTAINER, + ) + + IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + mark_populated=True, + ) + IPAddress.objects.create(address=IPNetwork('192.0.2.15/24'), vrf=vrf1) + + # The range covers 10 hosts; the VRF 1 IP inside it is not counted again. + self.assertEqual(prefix.get_available_ip_count(), 244) + self.assertAvailableIPCountMatchesIPSet(prefix) + def test_get_first_available_prefix(self): prefixes = Prefix.objects.bulk_create(( @@ -370,6 +900,42 @@ class PrefixTestCase(TestCase): parent_prefix = Prefix.objects.create(prefix=IPNetwork('2001:db8:500:5::/127')) self.assertEqual(parent_prefix.get_first_available_ip(), '2001:db8:500:5::/127') + def test_get_first_available_ip_ipv6_zero_address(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('::/126'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + # Normal IPv6 prefixes exclude the subnet-router anycast address ::. + self.assertEqual(prefix.get_first_available_ip(), '::1/126') + + def test_get_first_available_ip_populated_ranges(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/29'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.1/29')) + + IPRange.objects.create( + start_address=IPNetwork('192.0.2.2/29'), + end_address=IPNetwork('192.0.2.3/29'), + mark_populated=True, + ) + + self.assertEqual(prefix.get_first_available_ip(), '192.0.2.4/29') + + def test_get_first_available_ip_full(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/30'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.1/30')) + IPAddress.objects.create(address=IPNetwork('192.0.2.2/30')) + + self.assertIsNone(prefix.get_first_available_ip()) + def test_get_utilization_container(self): prefixes = ( Prefix(prefix=IPNetwork('10.0.0.0/24'), status=PrefixStatusChoices.STATUS_CONTAINER), @@ -399,6 +965,276 @@ class PrefixTestCase(TestCase): ) self.assertEqual(prefix.get_utilization(), 64 / 254 * 100) # ~25% utilization + def test_get_utilization_distinct_hosts(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.10/24')), + IPAddress(address=IPNetwork('192.0.2.10/32')), + IPAddress(address=IPNetwork('192.0.2.11/24')), + )) + + # Two unique occupied hosts over 254 usable IPv4 addresses. + self.assertEqual(prefix.get_utilization(), 2 / 254 * 100) + + @override_settings(ENFORCE_GLOBAL_UNIQUE=False) + def test_get_utilization_duplicate_ips_global(self): + """ + Tests that identical global IPs permitted by disabled uniqueness count as one host. + """ + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.10/24')) + duplicate_ip = IPAddress(address=IPNetwork('192.0.2.10/24')) + self.assertIsNone(duplicate_ip.clean()) + duplicate_ip.save() + + self.assertEqual(prefix.get_utilization(), 1 / 254 * 100) + + def test_get_utilization_duplicate_ips_vrf(self): + """ + Tests that identical IPs in a non-unique VRF count as one host. + """ + vrf = VRF.objects.create(name='VRF 1', enforce_unique=False) + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + vrf=vrf, + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.10/24'), vrf=vrf) + duplicate_ip = IPAddress(address=IPNetwork('192.0.2.10/24'), vrf=vrf) + self.assertIsNone(duplicate_ip.clean()) + duplicate_ip.save() + + self.assertEqual(prefix.get_utilization(), 1 / 254 * 100) + + def test_available_ip_count_duplicate_ips_vrf(self): + """ + Tests that identical IPs in a non-unique VRF reduce availability once. + """ + vrf = VRF.objects.create(name='VRF 1', enforce_unique=False) + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/29'), + vrf=vrf, + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.1/29'), vrf=vrf), + IPAddress(address=IPNetwork('192.0.2.1/29'), vrf=vrf), + )) + + # Usable hosts in /29: 6. The duplicate occupies a single host. + self.assertEqual(prefix.get_available_ip_count(), 5) + self.assertAvailableIPCountMatchesIPSet(prefix) + + @override_settings(ENFORCE_GLOBAL_UNIQUE=False) + def test_get_ip_usage_summary_duplicate_ips_global(self): + """ + Tests that the usage summary deduplicates identical global IPs in both values. + """ + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.10/24')), + IPAddress(address=IPNetwork('192.0.2.10/24')), + )) + + summary = prefix.get_ip_usage_summary() + + self.assertEqual(summary['available_ip_count'], 253) + self.assertEqual(summary['utilization'], 1 / 254 * 100) + + def test_get_utilization_utilized_ranges(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + mark_utilized=True, + ) + + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.1/24')), + IPAddress(address=IPNetwork('192.0.2.10/24')), + IPAddress(address=IPNetwork('192.0.2.11/24')), + IPAddress(address=IPNetwork('192.0.2.20/24')), + )) + + # Utilized range contributes 10 hosts; IPs inside the range are not double-counted. + # Outside IPs: .1 and .20 => 2 more. + self.assertEqual(prefix.get_utilization(), 12 / 254 * 100) + + def test_get_utilization_overlapping_utilized_ranges(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + mark_utilized=True, + ) + IPRange.objects.create( + start_address=IPNetwork('192.0.2.15/24'), + end_address=IPNetwork('192.0.2.24/24'), + mark_utilized=True, + ) + + # Union is .10-.24 => 15 hosts, not 20. + self.assertEqual(prefix.get_utilization(), 15 / 254 * 100) + + def test_get_utilization_fully_utilized_range(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + # Utilized range covers every usable host (.1-.254 in a non-pool /24). + IPRange.objects.create( + start_address=IPNetwork('192.0.2.1/24'), + end_address=IPNetwork('192.0.2.254/24'), + mark_utilized=True, + ) + + # Exercises the early-return path that skips the child-IP count entirely. + self.assertEqual(prefix.get_utilization(), 100) + + def test_get_utilization_ipv6_utilized_range(self): + prefix = Prefix.objects.create( + prefix=IPNetwork('2001:db8::/126'), + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPRange.objects.create( + start_address=IPNetwork('2001:db8::1/126'), + end_address=IPNetwork('2001:db8::2/126'), + mark_utilized=True, + ) + + self.assertEqual(prefix.get_utilization(), 2 / 4 * 100) + + def test_get_utilization_vrf(self): + vrf1 = VRF.objects.create(name='VRF 1') + vrf2 = VRF.objects.create(name='VRF 2') + + prefix = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + vrf=vrf1, + status=PrefixStatusChoices.STATUS_ACTIVE, + ) + + IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), vrf=vrf1) + IPAddress.objects.create(address=IPNetwork('192.0.2.15/24'), vrf=vrf1) + IPAddress.objects.create(address=IPNetwork('192.0.2.2/24'), vrf=vrf2) + IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + vrf=vrf2, + mark_utilized=True, + ) + + # VRF 2 objects are ignored entirely; the VRF 1 IP at .15 still counts even + # though it falls inside the VRF 2 range's host span (exclusion intervals are + # built only from same-VRF ranges). + self.assertEqual(prefix.get_utilization(), 2 / 254 * 100) + + def test_get_utilization_query_count(self): + """ + Tests that utilization for a non-container prefix uses two queries. + """ + prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24')) + + with self.assertNumQueries(2): + prefix.get_utilization() + + def test_get_ip_usage_summary(self): + """ + Tests that the combined summary matches the independent methods. + """ + prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24')) + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.1/24')), + IPAddress(address=IPNetwork('192.0.2.2/24')), + )) + IPRange.objects.create( + start_address=IPNetwork('192.0.2.10/24'), + end_address=IPNetwork('192.0.2.19/24'), + mark_utilized=True, + ) + IPRange.objects.create( + start_address=IPNetwork('192.0.2.30/24'), + end_address=IPNetwork('192.0.2.39/24'), + mark_populated=True, + ) + + summary = prefix.get_ip_usage_summary() + + self.assertEqual(summary['available_ip_count'], prefix.get_available_ip_count()) + self.assertEqual(summary['utilization'], prefix.get_utilization()) + + def test_get_ip_usage_summary_query_count(self): + """ + Tests that the combined summary uses a single distinct-host scan (three queries). + """ + prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24')) + + with self.assertNumQueries(3): + prefix.get_ip_usage_summary() + + def test_get_ip_usage_summary_container(self): + """ + Tests that the summary delegates to the independent methods for containers. + """ + container = Prefix.objects.create( + prefix=IPNetwork('192.0.2.0/24'), + status=PrefixStatusChoices.STATUS_CONTAINER, + ) + + summary = container.get_ip_usage_summary() + + self.assertEqual(summary['available_ip_count'], container.get_available_ip_count()) + self.assertEqual(summary['utilization'], container.get_utilization()) + + def test_get_ip_usage_summary_mark_utilized(self): + """ + Tests that a marked-utilized prefix reports 100% utilization in the summary. + """ + prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'), mark_utilized=True) + + summary = prefix.get_ip_usage_summary() + + self.assertEqual(summary['utilization'], 100) + self.assertEqual(summary['available_ip_count'], prefix.get_available_ip_count()) + + def test_usable_size(self): + self.assertEqual(Prefix(prefix=IPNetwork('192.0.2.0/24')).usable_size, 254) + self.assertEqual(Prefix(prefix=IPNetwork('192.0.2.0/24'), is_pool=True).usable_size, 256) + self.assertEqual(Prefix(prefix=IPNetwork('2001:db8::/126')).usable_size, 3) + + def test_usable_ip_bounds_string_prefix(self): + """ + Tests that usable bounds are computed for a string-assigned prefix. + """ + first_ip, last_ip = Prefix(prefix='192.0.2.0/24').usable_ip_bounds + + self.assertEqual(first_ip, netaddr.IPAddress('192.0.2.1')) + self.assertEqual(last_ip, netaddr.IPAddress('192.0.2.254')) + # # Uniqueness enforcement tests # @@ -624,6 +1460,35 @@ class PrefixHierarchyTestCase(TestCase): self.assertEqual(prefixes[3]._depth, 2) self.assertEqual(prefixes[3]._children, 0) + def test_rebuild_prefixes_accepts_vrf_identifier(self): + # None means "global table". Wipe the precomputed hierarchy so the rebuild is observable. + Prefix.objects.update(_depth=0, _children=0) + + rebuild_prefixes(None) + + top = Prefix.objects.get(prefix='10.0.0.0/8') + mid = Prefix.objects.get(prefix='10.0.0.0/16') + leaf = Prefix.objects.get(prefix='10.0.0.0/24') + self.assertEqual((top._depth, top._children), (0, 2)) + self.assertEqual((mid._depth, mid._children), (1, 1)) + self.assertEqual((leaf._depth, leaf._children), (2, 0)) + + def test_rebuild_prefixes_accepts_vrf_pk(self): + # A VRF pk filters to that VRF's prefixes. + vrf = VRF.objects.create(name='VRF 1') + Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'), vrf=vrf) + Prefix.objects.create(prefix=IPNetwork('192.0.2.0/25'), vrf=vrf) + + # Reset depth/children so the rebuild has something to restore. + Prefix.objects.filter(vrf=vrf).update(_depth=0, _children=0) + + rebuild_prefixes(vrf.pk) + + parent = Prefix.objects.get(prefix='192.0.2.0/24', vrf=vrf) + child = Prefix.objects.get(prefix='192.0.2.0/25', vrf=vrf) + self.assertEqual((parent._depth, parent._children), (0, 1)) + self.assertEqual((child._depth, child._children), (1, 0)) + class IPAddressTestCase(TestCase): @@ -719,6 +1584,20 @@ class IPAddressTestCase(TestCase): with self.assertRaisesMessage(ValidationError, 'Cannot create IP address'): ipaddress.clean() + def test_populated_range_blocks_ip_with_different_mask(self): + # The populated-range check compares by host portion, so a different mask + # must not let an IPAddress slip past validation. + IPRange.objects.create( + start_address=IPNetwork('10.0.0.2/24'), + end_address=IPNetwork('10.0.0.254/24'), + mark_populated=True, + ) + + ip = IPAddress(address=IPNetwork('10.0.0.2/32')) + + with self.assertRaises(ValidationError): + ip.full_clean() + class VLANGroupTestCase(TestCase): @@ -930,6 +1809,45 @@ class VLANTestCase(TestCase): vlan.full_clean() +class PrefixGetChildIPsTestCase(TestCase): + @classmethod + def setUpTestData(cls): + cls.prefix = Prefix.objects.create(prefix='10.0.0.0/24') + IPAddress.objects.bulk_create(( + IPAddress(address='10.0.0.0/24'), # Network address (inside containment) + IPAddress(address='10.0.0.1/24'), + IPAddress(address='10.0.0.255/24'), # Broadcast address (inside containment) + IPAddress(address='10.0.1.1/24'), # Outside the prefix + )) + + def test_get_child_ips_matches_net_host_contained(self): + """get_child_ips returns the same IPs as the net_host_contained containment lookup.""" + expected = set( + IPAddress.objects.filter( + address__net_host_contained=str(self.prefix.prefix), vrf=None + ).values_list('pk', flat=True) + ) + actual = set(self.prefix.get_child_ips().values_list('pk', flat=True)) + self.assertEqual(actual, expected) + self.assertEqual(len(actual), 3) + + def test_get_child_ips_sql_avoids_containment_recheck(self): + """get_child_ips filters on an inet host range, not the <<= containment operator.""" + sql = str(self.prefix.get_child_ips().query) + self.assertNotIn('<<=', sql) + + def test_get_child_ips_container_in_global_table_spans_vrfs(self): + """A container prefix in the global table returns child IPs from any VRF.""" + vrf = VRF.objects.create(name='VRF 1') + container = Prefix.objects.create( + prefix='10.1.0.0/24', status=PrefixStatusChoices.STATUS_CONTAINER, + ) + in_vrf = IPAddress.objects.create(address='10.1.0.5/24', vrf=vrf) + in_global = IPAddress.objects.create(address='10.1.0.6/24') + child_pks = set(container.get_child_ips().values_list('pk', flat=True)) + self.assertEqual(child_pks, {in_vrf.pk, in_global.pk}) + + class ServiceTemplateTestCase(TestCase): def test_servicetemplate_lowest_port(self): diff --git a/netbox/ipam/tests/test_querysets.py b/netbox/ipam/tests/test_querysets.py new file mode 100644 index 000000000..feb8ff2cd --- /dev/null +++ b/netbox/ipam/tests/test_querysets.py @@ -0,0 +1,344 @@ +import netaddr +from django.test import TestCase +from netaddr import IPNetwork + +from ipam.models import IPAddress, IPRange + + +class IPAddressQuerySetTestCase(TestCase): + @classmethod + def setUpTestData(cls): + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.1/24')), + IPAddress(address=IPNetwork('192.0.2.1/32')), + IPAddress(address=IPNetwork('192.0.2.2/24')), + )) + + def test_count_distinct_hosts(self): + """ + Tests that duplicate hosts with different masks are counted once. + """ + self.assertEqual(IPAddress.objects.count_distinct_hosts(), 2) + + def test_count_distinct_hosts_empty(self): + """ + Tests that an empty queryset counts zero hosts. + """ + self.assertEqual(IPAddress.objects.none().count_distinct_hosts(), 0) + + def test_count_distinct_hosts_exclude_intervals(self): + """ + Tests that hosts covered by an excluded interval are not counted. + """ + interval = (netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.1')) + self.assertEqual(IPAddress.objects.count_distinct_hosts(exclude_intervals=[interval]), 1) + + def test_count_distinct_hosts_pair(self): + """ + Tests that the bounded and total distinct host counts are computed correctly. + """ + counts = IPAddress.objects.count_distinct_hosts_pair( + bounds=(netaddr.IPAddress('192.0.2.2'), netaddr.IPAddress('192.0.2.10')), + bounded_exclude=[(netaddr.IPAddress('192.0.2.2'), netaddr.IPAddress('192.0.2.2'))], + total_exclude=[(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.1'))], + ) + self.assertEqual(counts, {'bounded': 0, 'total': 1}) + + def test_count_distinct_hosts_pair_no_excludes(self): + """ + Tests that both counts dedupe hosts and respect the bounds without excludes. + """ + counts = IPAddress.objects.count_distinct_hosts_pair( + bounds=(netaddr.IPAddress('192.0.2.2'), netaddr.IPAddress('192.0.2.10')), + ) + self.assertEqual(counts, {'bounded': 1, 'total': 2}) + + def test_first_available_host(self): + """ + Tests that occupied hosts and excluded intervals are skipped, including hosts behind the sweep. + """ + interval = (netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.5')) + self.assertEqual( + IPAddress.objects.first_available_host( + netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), exclude_intervals=[interval] + ), + netaddr.IPAddress('192.0.2.6'), + ) + + def test_first_available_host_inverted_bounds(self): + """ + Tests that an inverted bounds pair yields None. + """ + self.assertIsNone( + IPAddress.objects.first_available_host(netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.5')) + ) + + def test_available_intervals(self): + """ + Tests that gaps around occupied hosts and excluded intervals are yielded in order. + """ + interval = (netaddr.IPAddress('192.0.2.5'), netaddr.IPAddress('192.0.2.6')) + self.assertEqual( + list(IPAddress.objects.available_intervals( + netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), exclude_intervals=[interval] + )), + [ + (netaddr.IPAddress('192.0.2.3'), netaddr.IPAddress('192.0.2.4')), + (netaddr.IPAddress('192.0.2.7'), netaddr.IPAddress('192.0.2.10')), + ], + ) + + def test_available_intervals_leading_gap(self): + """ + Tests that the gap before the first occupied host is yielded. + """ + self.assertEqual( + list(IPAddress.objects.available_intervals( + netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.2') + )), + [(netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.0'))], + ) + + def test_available_intervals_empty_queryset(self): + """ + Tests that an empty queryset yields the full span. + """ + self.assertEqual( + list(IPAddress.objects.none().available_intervals( + netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.3') + )), + [(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.3'))], + ) + + def test_available_intervals_inverted_bounds(self): + """ + Tests that an inverted bounds pair yields nothing. + """ + self.assertEqual( + list(IPAddress.objects.available_intervals( + netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.5') + )), + [], + ) + + def test_available_intervals_fully_excluded(self): + """ + Tests that a span covered by an excluded interval yields nothing. + """ + interval = (netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.20')) + self.assertEqual( + list(IPAddress.objects.available_intervals( + netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), exclude_intervals=[interval] + )), + [], + ) + + def test_available_intervals_mixed_family_exclude(self): + """ + Tests that an exclude interval spanning address families is ignored. + """ + interval = (netaddr.IPAddress('192.0.2.5'), netaddr.IPAddress('2001:db8::5')) + self.assertEqual( + list(IPAddress.objects.available_intervals( + netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), exclude_intervals=[interval] + )), + [(netaddr.IPAddress('192.0.2.3'), netaddr.IPAddress('192.0.2.10'))], + ) + + def test_available_intervals_invalid_batch_size(self): + """ + Tests that a non-positive batch size raises ValueError. + """ + intervals = IPAddress.objects.available_intervals( + netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), batch_size=0 + ) + with self.assertRaises(ValueError): + next(intervals) + + def test_available_intervals_first_interval_single_query(self): + """ + Tests that consuming only the first interval issues a single batch query. + """ + IPAddress.objects.bulk_create(( + IPAddress(address=IPNetwork('192.0.2.12/24')), + IPAddress(address=IPNetwork('192.0.2.14/24')), + IPAddress(address=IPNetwork('192.0.2.16/24')), + )) + + intervals = IPAddress.objects.available_intervals( + netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.20'), batch_size=1 + ) + + with self.assertNumQueries(1): + self.assertEqual( + next(intervals), + (netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.11')), + ) + + def test_available_intervals_unsorted_exclude_intervals(self): + """ + Tests that unsorted, overlapping exclude intervals are normalized internally. + """ + intervals = list(IPAddress.objects.none().available_intervals( + netaddr.IPAddress('192.0.2.1'), + netaddr.IPAddress('192.0.2.40'), + exclude_intervals=[ + (netaddr.IPAddress('192.0.2.20'), netaddr.IPAddress('192.0.2.30')), + (netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10')), + (netaddr.IPAddress('192.0.2.25'), netaddr.IPAddress('192.0.2.30')), + ], + )) + + self.assertEqual(intervals, [ + (netaddr.IPAddress('192.0.2.11'), netaddr.IPAddress('192.0.2.19')), + (netaddr.IPAddress('192.0.2.31'), netaddr.IPAddress('192.0.2.40')), + ]) + + def test_available_intervals_batching(self): + """ + Tests that gaps spanning multiple fetch batches are yielded completely and in order. + """ + IPAddress.objects.bulk_create( + IPAddress(address=IPNetwork(f'192.0.3.{i}/24')) for i in range(2, 82, 2) + ) + expected = [ + (netaddr.IPAddress(f'192.0.3.{i}'), netaddr.IPAddress(f'192.0.3.{i}')) + for i in range(1, 83, 2) + ] + self.assertEqual( + list(IPAddress.objects.available_intervals( + netaddr.IPAddress('192.0.3.1'), netaddr.IPAddress('192.0.3.81'), batch_size=8 + )), + expected, + ) + + def test_iter_distinct_hosts_stops_at_upper_bound(self): + """ + Tests that batch resumption stops once the last fetched host reaches the upper bound. + """ + IPAddress.objects.bulk_create( + IPAddress(address=IPNetwork(f'192.0.4.{i}/24')) for i in (2, 4) + ) + self.assertEqual( + list(IPAddress.objects.all()._iter_distinct_hosts( + netaddr.IPAddress('192.0.4.2'), netaddr.IPAddress('192.0.4.4'), batch_size=1 + )), + [netaddr.IPAddress('192.0.4.2'), netaddr.IPAddress('192.0.4.4')], + ) + + def test_available_intervals_batch_size_one(self): + """ + Tests that fetching one host per batch still terminates and yields every gap. + """ + IPAddress.objects.bulk_create( + IPAddress(address=IPNetwork(f'192.0.3.{i}/24')) for i in (2, 3, 5) + ) + self.assertEqual( + list(IPAddress.objects.available_intervals( + netaddr.IPAddress('192.0.3.1'), netaddr.IPAddress('192.0.3.6'), batch_size=1 + )), + [ + (netaddr.IPAddress('192.0.3.1'), netaddr.IPAddress('192.0.3.1')), + (netaddr.IPAddress('192.0.3.4'), netaddr.IPAddress('192.0.3.4')), + (netaddr.IPAddress('192.0.3.6'), netaddr.IPAddress('192.0.3.6')), + ], + ) + + +class IPRangeQuerySetTestCase(TestCase): + @classmethod + def setUpTestData(cls): + IPRange.objects.bulk_create(( + IPRange(start_address=IPNetwork('192.0.2.10/24'), end_address=IPNetwork('192.0.2.19/24'), size=10), + IPRange(start_address=IPNetwork('192.0.2.15/24'), end_address=IPNetwork('192.0.2.24/24'), size=10), + IPRange(start_address=IPNetwork('192.0.2.40/24'), end_address=IPNetwork('192.0.2.49/24'), size=10), + )) + + def test_get_intervals_merges_overlaps(self): + """ + Tests that overlapping ranges merge and disjoint ranges stay separate. + """ + self.assertEqual( + IPRange.objects.get_intervals(), + [ + (netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.24')), + (netaddr.IPAddress('192.0.2.40'), netaddr.IPAddress('192.0.2.49')), + ], + ) + + def test_get_intervals_clips_to_bounds(self): + """ + Tests that ranges are clipped to the bounds and out-of-bounds ranges are dropped. + """ + self.assertEqual( + IPRange.objects.get_intervals(netaddr.IPAddress('192.0.2.20'), netaddr.IPAddress('192.0.2.30')), + [(netaddr.IPAddress('192.0.2.20'), netaddr.IPAddress('192.0.2.24'))], + ) + + def test_get_intervals_drops_ranges_below_bounds(self): + """ + Tests that ranges entirely below the lower bound are dropped. + """ + self.assertEqual( + IPRange.objects.get_intervals(netaddr.IPAddress('192.0.2.30'), netaddr.IPAddress('192.0.2.60')), + [(netaddr.IPAddress('192.0.2.40'), netaddr.IPAddress('192.0.2.49'))], + ) + + def test_get_intervals_drops_ranges_above_bounds(self): + """ + Tests that ranges entirely above the upper bound are dropped. + """ + self.assertEqual( + IPRange.objects.get_intervals(netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.30')), + [(netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.24'))], + ) + + def test_get_intervals_clips_to_upper_bound(self): + """ + Tests that a range straddling the upper bound is clipped to it. + """ + self.assertEqual( + IPRange.objects.get_intervals(netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.15')), + [(netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.15'))], + ) + + def test_get_intervals_mixed_families(self): + """ + Tests that int-adjacent intervals of different address families are not merged. + """ + IPRange.objects.bulk_create(( + IPRange( + start_address=IPNetwork('255.255.255.254/32'), + end_address=IPNetwork('255.255.255.255/32'), + size=2, + ), + IPRange(start_address=IPNetwork('::1/128'), end_address=IPNetwork('::2/128'), size=2), + )) + + self.assertEqual( + IPRange.objects.get_intervals(), + [ + (netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.24')), + (netaddr.IPAddress('192.0.2.40'), netaddr.IPAddress('192.0.2.49')), + (netaddr.IPAddress('255.255.255.254'), netaddr.IPAddress('255.255.255.255')), + (netaddr.IPAddress('::1'), netaddr.IPAddress('::2')), + ], + ) + + def test_get_intervals_ipv6(self): + """ + Tests that IPv6 ranges merge and clip by host address. + """ + IPRange.objects.create( + start_address=IPNetwork('2001:db8::10/64'), + end_address=IPNetwork('2001:db8::1f/64'), + ) + IPRange.objects.create( + start_address=IPNetwork('2001:db8::18/64'), + end_address=IPNetwork('2001:db8::2f/64'), + ) + + self.assertEqual( + IPRange.objects.get_intervals(netaddr.IPAddress('2001:db8::'), netaddr.IPAddress('2001:db8::ffff')), + [(netaddr.IPAddress('2001:db8::10'), netaddr.IPAddress('2001:db8::2f'))], + ) diff --git a/netbox/ipam/utils.py b/netbox/ipam/utils.py index 390215873..5b29688fd 100644 --- a/netbox/ipam/utils.py +++ b/netbox/ipam/utils.py @@ -1,10 +1,10 @@ from dataclasses import dataclass import netaddr +from django.apps import apps from django.utils.translation import gettext_lazy as _ from .constants import * -from .models import VLAN, Prefix __all__ = ( 'AvailableIPSpace', @@ -39,7 +39,7 @@ def add_requested_prefixes(parent, prefix_list, show_available=True, show_assign requested, create fake Prefix objects for all unallocated space within a prefix. :param parent: Parent Prefix instance - :param prefix_list: Child prefixes list + :param prefix_list: Child prefixes list (or queryset) :param show_available: Include available prefixes. :param show_assigned: Show assigned prefixes. """ @@ -47,6 +47,7 @@ def add_requested_prefixes(parent, prefix_list, show_available=True, show_assign # Add available prefixes to the table if requested if prefix_list and show_available: + Prefix = apps.get_model('ipam', 'Prefix') # Find all unallocated space, add fake Prefix objects to child_prefixes. # IMPORTANT: These are unsaved Prefix instances (pk=None). If this is ever changed to use @@ -78,22 +79,7 @@ def annotate_ip_space(prefix): records = sorted(records, key=lambda x: x[0]) # Determine the first & last valid IP addresses in the prefix - if ( - prefix.is_pool - or (prefix.family == 4 and prefix.mask_length >= 31) - or (prefix.family == 6 and prefix.mask_length >= 127) - ): - # Pool, IPv4 /31-/32 or IPv6 /127-/128 sets are fully usable - first_ip_in_prefix = netaddr.IPAddress(prefix.prefix.first) - last_ip_in_prefix = netaddr.IPAddress(prefix.prefix.last) - elif prefix.family == 4: - # Ignore the network and broadcast addresses for non-pool IPv4 prefixes larger than /31 - first_ip_in_prefix = netaddr.IPAddress(prefix.prefix.first + 1) - last_ip_in_prefix = netaddr.IPAddress(prefix.prefix.last - 1) - else: - # For IPv6 prefixes, omit the Subnet-Router anycast address (RFC 4291) - first_ip_in_prefix = netaddr.IPAddress(prefix.prefix.first + 1) - last_ip_in_prefix = netaddr.IPAddress(prefix.prefix.last) + first_ip_in_prefix, last_ip_in_prefix = prefix.usable_ip_bounds if not records: return [ @@ -195,7 +181,7 @@ def add_available_vlans(vlans, vlan_group): new_vlans.extend(available_vlans_from_range(vlans, vlan_group, vid_range)) vlans = list(vlans) + new_vlans - vlans.sort(key=lambda v: v.vid if type(v) is VLAN else v['vid']) + vlans.sort(key=lambda v: v['vid'] if isinstance(v, dict) else v.vid) return vlans @@ -204,6 +190,9 @@ def rebuild_prefixes(vrf): """ Rebuild the prefix hierarchy for all prefixes in the specified VRF (or global table). """ + Prefix = apps.get_model('ipam', 'Prefix') + prefix_queryset = Prefix.objects.filter(vrf=vrf) + def contains(parent, child): return child in parent and child != parent @@ -219,10 +208,10 @@ def rebuild_prefixes(vrf): stack = [] update_queue = [] - prefixes = Prefix.objects.filter(vrf=vrf).values('pk', 'prefix') + prefixes = prefix_queryset.order_by('prefix', 'pk').values('pk', 'prefix') - # Iterate through all Prefixes in the VRF, growing and shrinking the stack as we go - for i, p in enumerate(prefixes): + # Iterate through all Prefixes in the table, growing and shrinking the stack as we go + for p in prefixes: # Grow the stack if this is a child of the most recent prefix if not stack or contains(stack[-1]['prefix'], p['prefix']): diff --git a/netbox/templates/ipam/iprange/ip_addresses.html b/netbox/templates/ipam/iprange/ip_addresses.html index f6c8e9101..c80e71707 100644 --- a/netbox/templates/ipam/iprange/ip_addresses.html +++ b/netbox/templates/ipam/iprange/ip_addresses.html @@ -2,9 +2,11 @@ {% load i18n %} {% block extra_controls %} - {% if perms.ipam.add_ipaddress and object.first_available_ip %} - - {% trans "Add IP Address" %} - - {% endif %} + {% with first_available_ip=object.get_first_available_ip %} + {% if perms.ipam.add_ipaddress and first_available_ip %} + + {% trans "Add IP Address" %} + + {% endif %} + {% endwith %} {% endblock extra_controls %} diff --git a/netbox/templates/ipam/panels/prefix_addressing.html b/netbox/templates/ipam/panels/prefix_addressing.html index ff74d6c0a..04501ec50 100644 --- a/netbox/templates/ipam/panels/prefix_addressing.html +++ b/netbox/templates/ipam/panels/prefix_addressing.html @@ -11,6 +11,7 @@ {% endif %} + {% with usage=object.get_ip_usage_summary %} @@ -30,33 +31,36 @@ {% endwith %} - {% with available_count=object.get_available_ips.size %} - - - - - {% endwith %} + + + + + {% endwith %}
{% trans "Utilization" %} @@ -18,7 +19,7 @@ {% utilization_graph 100 warning_threshold=0 danger_threshold=0 %} ({% trans "Marked fully utilized" %}) {% else %} - {% utilization_graph object.get_utilization %} + {% utilization_graph usage.utilization %} {% endif %}
{% trans "Available IPs" %} - {% if available_count > 1000000 %} - {{ available_count|intword }} - {% else %} - {{ available_count|intcomma }} - {% endif %} -
{% trans "Available IPs" %} + {% if usage.available_ip_count > 1000000 %} + {{ usage.available_ip_count|intword }} + {% else %} + {{ usage.available_ip_count|intcomma }} + {% endif %} +
{% trans "First available IP" %} - {% with first_available_ip=object.get_first_available_ip %} - {% if first_available_ip %} - {% if perms.ipam.add_ipaddress %} - {{ first_available_ip }} + {% if usage.available_ip_count %} + {% with first_available_ip=object.get_first_available_ip %} + {% if first_available_ip %} + {% if perms.ipam.add_ipaddress %} + {{ first_available_ip }} + {% else %} + {{ first_available_ip }} + {% endif %} {% else %} - {{ first_available_ip }} + {{ ''|placeholder }} {% endif %} - {% else %} - {{ ''|placeholder }} - {% endif %} - {% endwith %} + {% endwith %} + {% else %} + {{ ''|placeholder }} + {% endif %}
From b4fdd6f20985cea8482acafbefd27c1a426ecaa9 Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Tue, 2 Jun 2026 14:05:28 +0200 Subject: [PATCH 41/58] Closes #22333: Use lowercase username for testing The test failures arises from unstable sorting of the usernames depending on the collation used in the PostgreSQL database used for testing. When a case-insensitive collation is used 'testuser' is sorted before 'User*' and because this user has permissions assigned and additional query is issued resulting in 12 queries. When a case-sensitive collation is used the sorting is inverted. Because the 'User*' don't have permissions only 11 queries are sent to the database. Using only testusers with lowercase names enforces stable sorting across collations. --- netbox/users/tests/test_api.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/netbox/users/tests/test_api.py b/netbox/users/tests/test_api.py index de57d8696..5606e905f 100644 --- a/netbox/users/tests/test_api.py +++ b/netbox/users/tests/test_api.py @@ -39,25 +39,25 @@ class UserTestCase(APIViewTestCases.APIViewTestCase): permissions[2].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'rack')) users = ( - User(username='User1', password='FooBarFooBar1'), - User(username='User2', password='FooBarFooBar2'), - User(username='User3', password='FooBarFooBar3'), + User(username='user1', password='FooBarFooBar1'), + User(username='user2', password='FooBarFooBar2'), + User(username='user3', password='FooBarFooBar3'), ) User.objects.bulk_create(users) cls.create_data = [ { - 'username': 'User4', + 'username': 'user4', 'password': 'FooBarFooBar4', 'permissions': [permissions[0].pk], }, { - 'username': 'User5', + 'username': 'user5', 'password': 'FooBarFooBar5', 'permissions': [permissions[1].pk], }, { - 'username': 'User6', + 'username': 'user6', 'password': 'FooBarFooBar6', 'permissions': [permissions[2].pk], }, From 8ff56032b9538e57d7e9a795ceeb4f37f118e2f0 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Sat, 13 Jun 2026 20:10:29 -0400 Subject: [PATCH 42/58] Fixes #22444: Fix KeyError exception on device view with non-English locale (#22445) --- netbox/dcim/ui/panels.py | 6 +++--- netbox/virtualization/ui/panels.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/ui/panels.py b/netbox/dcim/ui/panels.py index 478ddf634..bd2adc7f6 100644 --- a/netbox/dcim/ui/panels.py +++ b/netbox/dcim/ui/panels.py @@ -32,7 +32,7 @@ class RackDimensionsPanel(panels.ObjectAttributesPanel): outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display') outer_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display') outer_depth = attrs.NumericAttr('outer_depth', unit_accessor='get_outer_unit_display') - mounting_depth = attrs.TextAttr('mounting_depth', format_string=_('{} millimeters')) + mounting_depth = attrs.TextAttr('mounting_depth', format_string=_('{0} millimeters')) class RackNumberingPanel(panels.ObjectAttributesPanel): @@ -355,8 +355,8 @@ class PowerFeedElectricalPanel(panels.ObjectAttributesPanel): title = _('Electrical Characteristics') supply = attrs.ChoiceAttr('supply') - voltage = attrs.TextAttr('voltage', format_string=_('{}V')) - amperage = attrs.TextAttr('amperage', format_string=_('{}A')) + voltage = attrs.TextAttr('voltage', format_string='{}V') + amperage = attrs.TextAttr('amperage', format_string='{}A') phase = attrs.ChoiceAttr('phase') max_utilization = attrs.TextAttr('max_utilization', format_string='{}%') diff --git a/netbox/virtualization/ui/panels.py b/netbox/virtualization/ui/panels.py index f562efc77..4132f0544 100644 --- a/netbox/virtualization/ui/panels.py +++ b/netbox/virtualization/ui/panels.py @@ -26,7 +26,7 @@ class VirtualMachineTypePanel(panels.ObjectAttributesPanel): name = attrs.TextAttr('name') default_platform = attrs.RelatedObjectAttr('default_platform', linkify=True) default_vcpus = attrs.TextAttr('default_vcpus', label=_('Default vCPUs')) - default_memory = attrs.TextAttr('default_memory', format_string=_('{} MB'), label=_('Default memory')) + default_memory = attrs.TextAttr('default_memory', format_string=_('{0} MB'), label=_('Default memory')) description = attrs.TextAttr('description') From 850aae2d3525ccd1a0f054a39633fd0e64b9d6a2 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 14 Jun 2026 06:31:05 +0000 Subject: [PATCH 43/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 108 +++++++++---------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index d8bbc6858..67ffa3b98 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-12 06:30+0000\n" +"POT-Creation-Date: 2026-06-14 06:30+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1303,8 +1303,8 @@ msgstr "" #: netbox/dcim/models/modules.py:264 netbox/dcim/models/power.py:95 #: netbox/dcim/models/racks.py:329 netbox/dcim/models/racks.py:713 #: netbox/dcim/models/sites.py:163 netbox/dcim/models/sites.py:287 -#: netbox/ipam/models/ip.py:252 netbox/ipam/models/ip.py:554 -#: netbox/ipam/models/ip.py:792 netbox/ipam/models/vlans.py:242 +#: netbox/ipam/models/ip.py:252 netbox/ipam/models/ip.py:680 +#: netbox/ipam/models/ip.py:971 netbox/ipam/models/vlans.py:242 #: netbox/virtualization/models/clusters.py:70 #: netbox/virtualization/models/virtualmachines.py:169 #: netbox/vpn/models/l2vpn.py:36 netbox/vpn/models/tunnels.py:38 @@ -1529,7 +1529,7 @@ msgstr "" #: netbox/circuits/models/virtual_circuits.py:138 #: netbox/ipam/models/asns.py:146 netbox/ipam/models/ip.py:209 -#: netbox/ipam/models/ip.py:799 netbox/vpn/models/tunnels.py:109 +#: netbox/ipam/models/ip.py:978 netbox/vpn/models/tunnels.py:109 msgid "role" msgstr "" @@ -2467,7 +2467,7 @@ msgstr "" msgid "File path relative to the data source's root" msgstr "" -#: netbox/core/models/data.py:311 netbox/ipam/models/ip.py:535 +#: netbox/core/models/data.py:311 netbox/ipam/models/ip.py:661 #: netbox/virtualization/models/virtualmachines.py:570 msgid "size" msgstr "" @@ -4028,7 +4028,7 @@ msgstr "" #: netbox/ipam/forms/model_forms.py:274 netbox/ipam/forms/model_forms.py:327 #: netbox/ipam/forms/model_forms.py:490 netbox/ipam/forms/model_forms.py:510 #: netbox/ipam/forms/model_forms.py:524 netbox/ipam/models/ip.py:232 -#: netbox/ipam/models/ip.py:544 netbox/ipam/models/ip.py:782 +#: netbox/ipam/models/ip.py:670 netbox/ipam/models/ip.py:961 #: netbox/ipam/models/vrfs.py:64 netbox/ipam/tables/ip.py:192 #: netbox/ipam/tables/ip.py:263 netbox/ipam/tables/ip.py:316 #: netbox/ipam/tables/ip.py:418 netbox/ipam/ui/panels.py:103 @@ -8046,7 +8046,8 @@ msgid "Test case must set peer_termination_type" msgstr "" #: netbox/dcim/ui/panels.py:35 -msgid "{} millimeters" +#, python-brace-format +msgid "{0} millimeters" msgstr "" #: netbox/dcim/ui/panels.py:55 netbox/dcim/ui/panels.py:98 @@ -8097,14 +8098,6 @@ msgstr "" msgid "Electrical Characteristics" msgstr "" -#: netbox/dcim/ui/panels.py:358 -msgid "{}V" -msgstr "" - -#: netbox/dcim/ui/panels.py:359 -msgid "{}A" -msgstr "" - #: netbox/dcim/ui/panels.py:386 msgid "Primary for interface" msgstr "" @@ -10791,7 +10784,7 @@ msgstr "" msgid "IP address (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1272 netbox/ipam/models/ip.py:851 +#: netbox/ipam/filtersets.py:1272 netbox/ipam/models/ip.py:1030 msgid "IP address" msgstr "" @@ -10911,7 +10904,7 @@ msgstr "" msgid "Treat as populated" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:312 netbox/ipam/models/ip.py:834 +#: netbox/ipam/forms/bulk_edit.py:312 netbox/ipam/models/ip.py:1013 msgid "DNS name" msgstr "" @@ -11459,7 +11452,7 @@ msgstr "" msgid "All IP addresses within this prefix are considered usable" msgstr "" -#: netbox/ipam/models/ip.py:269 netbox/ipam/models/ip.py:574 +#: netbox/ipam/models/ip.py:269 netbox/ipam/models/ip.py:700 msgid "mark utilized" msgstr "" @@ -11471,12 +11464,12 @@ msgstr "" msgid "Cannot create prefix with /0 mask." msgstr "" -#: netbox/ipam/models/ip.py:324 netbox/ipam/models/ip.py:941 +#: netbox/ipam/models/ip.py:324 netbox/ipam/models/ip.py:1120 #, python-brace-format msgid "VRF {vrf}" msgstr "" -#: netbox/ipam/models/ip.py:324 netbox/ipam/models/ip.py:941 +#: netbox/ipam/models/ip.py:324 netbox/ipam/models/ip.py:1120 msgid "global table" msgstr "" @@ -11485,137 +11478,137 @@ msgstr "" msgid "Duplicate prefix found in {table}: {prefix}" msgstr "" -#: netbox/ipam/models/ip.py:527 +#: netbox/ipam/models/ip.py:653 msgid "start address" msgstr "" -#: netbox/ipam/models/ip.py:528 netbox/ipam/models/ip.py:532 -#: netbox/ipam/models/ip.py:774 +#: netbox/ipam/models/ip.py:654 netbox/ipam/models/ip.py:658 +#: netbox/ipam/models/ip.py:953 msgid "IPv4 or IPv6 address (with mask)" msgstr "" -#: netbox/ipam/models/ip.py:531 +#: netbox/ipam/models/ip.py:657 msgid "end address" msgstr "" -#: netbox/ipam/models/ip.py:558 +#: netbox/ipam/models/ip.py:684 msgid "Operational status of this range" msgstr "" -#: netbox/ipam/models/ip.py:566 +#: netbox/ipam/models/ip.py:692 msgid "The primary function of this range" msgstr "" -#: netbox/ipam/models/ip.py:569 +#: netbox/ipam/models/ip.py:695 msgid "mark populated" msgstr "" -#: netbox/ipam/models/ip.py:571 +#: netbox/ipam/models/ip.py:697 msgid "Prevent the creation of IP addresses within this range" msgstr "" -#: netbox/ipam/models/ip.py:576 +#: netbox/ipam/models/ip.py:702 msgid "Report space as fully utilized" msgstr "" -#: netbox/ipam/models/ip.py:585 +#: netbox/ipam/models/ip.py:723 msgid "IP range" msgstr "" -#: netbox/ipam/models/ip.py:586 +#: netbox/ipam/models/ip.py:724 msgid "IP ranges" msgstr "" -#: netbox/ipam/models/ip.py:599 +#: netbox/ipam/models/ip.py:737 msgid "Starting and ending IP address versions must match" msgstr "" -#: netbox/ipam/models/ip.py:605 +#: netbox/ipam/models/ip.py:743 msgid "Starting and ending IP address masks must match" msgstr "" -#: netbox/ipam/models/ip.py:613 +#: netbox/ipam/models/ip.py:751 #, python-brace-format msgid "" "Ending address must be greater than or equal to the starting address " "({start_address})" msgstr "" -#: netbox/ipam/models/ip.py:641 +#: netbox/ipam/models/ip.py:779 #, python-brace-format msgid "Defined addresses overlap with range {overlapping_range} in VRF {vrf}" msgstr "" -#: netbox/ipam/models/ip.py:650 +#: netbox/ipam/models/ip.py:788 #, python-brace-format msgid "Defined range exceeds maximum supported size ({max_size})" msgstr "" -#: netbox/ipam/models/ip.py:773 netbox/tenancy/models/contacts.py:104 +#: netbox/ipam/models/ip.py:952 netbox/tenancy/models/contacts.py:104 msgid "address" msgstr "" -#: netbox/ipam/models/ip.py:796 +#: netbox/ipam/models/ip.py:975 msgid "The operational status of this IP" msgstr "" -#: netbox/ipam/models/ip.py:804 +#: netbox/ipam/models/ip.py:983 msgid "The functional role of this IP" msgstr "" -#: netbox/ipam/models/ip.py:827 netbox/ipam/ui/panels.py:127 +#: netbox/ipam/models/ip.py:1006 netbox/ipam/ui/panels.py:127 msgid "NAT (inside)" msgstr "" -#: netbox/ipam/models/ip.py:828 +#: netbox/ipam/models/ip.py:1007 msgid "The IP for which this address is the \"outside\" IP" msgstr "" -#: netbox/ipam/models/ip.py:835 +#: netbox/ipam/models/ip.py:1014 msgid "Hostname or FQDN (not case-sensitive)" msgstr "" -#: netbox/ipam/models/ip.py:852 netbox/ipam/models/services.py:97 +#: netbox/ipam/models/ip.py:1031 netbox/ipam/models/services.py:97 msgid "IP addresses" msgstr "" -#: netbox/ipam/models/ip.py:912 +#: netbox/ipam/models/ip.py:1091 msgid "Cannot create IP address with /0 mask." msgstr "" -#: netbox/ipam/models/ip.py:918 +#: netbox/ipam/models/ip.py:1097 #, python-brace-format msgid "{ip} is a network ID, which may not be assigned to an interface." msgstr "" -#: netbox/ipam/models/ip.py:929 +#: netbox/ipam/models/ip.py:1108 #, python-brace-format msgid "{ip} is a broadcast address, which may not be assigned to an interface." msgstr "" -#: netbox/ipam/models/ip.py:943 +#: netbox/ipam/models/ip.py:1122 #, python-brace-format msgid "Duplicate IP address found in {table}: {ipaddress}" msgstr "" -#: netbox/ipam/models/ip.py:959 +#: netbox/ipam/models/ip.py:1138 #, python-brace-format msgid "Cannot create IP address {ip} inside range {range}." msgstr "" -#: netbox/ipam/models/ip.py:980 +#: netbox/ipam/models/ip.py:1159 msgid "" "Cannot reassign IP address while it is designated as the primary IP for the " "parent object" msgstr "" -#: netbox/ipam/models/ip.py:987 +#: netbox/ipam/models/ip.py:1166 msgid "" "Cannot reassign IP address while it is designated as the OOB IP for the " "parent object" msgstr "" -#: netbox/ipam/models/ip.py:993 +#: netbox/ipam/models/ip.py:1172 msgid "Only IPv6 addresses can be assigned SLAAC status" msgstr "" @@ -11810,7 +11803,7 @@ msgstr "" #: netbox/ipam/tables/ip.py:282 netbox/ipam/tables/vlans.py:59 #: netbox/ipam/ui/panels.py:80 netbox/ipam/ui/panels.py:101 #: netbox/templates/dcim/panels/power_utilization.html:12 -#: netbox/templates/ipam/panels/prefix_addressing.html:15 +#: netbox/templates/ipam/panels/prefix_addressing.html:16 msgid "Utilization" msgstr "" @@ -12066,7 +12059,7 @@ msgstr "" msgid "Virtual IP addresses" msgstr "" -#: netbox/ipam/views.py:1579 netbox/templates/ipam/iprange/ip_addresses.html:7 +#: netbox/ipam/views.py:1579 netbox/templates/ipam/iprange/ip_addresses.html:8 #: netbox/templates/ipam/prefix/ip_addresses.html:7 msgid "Add IP Address" msgstr "" @@ -15391,11 +15384,11 @@ msgstr "" msgid "Addressing Details" msgstr "" -#: netbox/templates/ipam/panels/prefix_addressing.html:19 +#: netbox/templates/ipam/panels/prefix_addressing.html:20 msgid "Marked fully utilized" msgstr "" -#: netbox/templates/ipam/panels/prefix_addressing.html:27 +#: netbox/templates/ipam/panels/prefix_addressing.html:28 msgid "Child IPs" msgstr "" @@ -15403,7 +15396,7 @@ msgstr "" msgid "Available IPs" msgstr "" -#: netbox/templates/ipam/panels/prefix_addressing.html:46 +#: netbox/templates/ipam/panels/prefix_addressing.html:45 msgid "First available IP" msgstr "" @@ -17179,7 +17172,8 @@ msgid "VM count" msgstr "" #: netbox/virtualization/ui/panels.py:29 -msgid "{} MB" +#, python-brace-format +msgid "{0} MB" msgstr "" #: netbox/virtualization/ui/panels.py:29 From bf1a27b89c02298c9ee18a451fb9cbde4b15418f Mon Sep 17 00:00:00 2001 From: Fabi <18670690+fabi125@users.noreply.github.com> Date: Sun, 14 Jun 2026 10:34:51 -0400 Subject: [PATCH 44/58] Fixes #22397: Fix AttributeError exception for unauthentictaed users during bulk export --- netbox/extras/tests/test_views.py | 9 ++++++++ netbox/netbox/views/generic/bulk_views.py | 4 ++-- netbox/utilities/testing/views.py | 25 +++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index f97b3581b..8dd82c239 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -408,6 +408,9 @@ class BookmarkTestCase( def test_list_objects_anonymous(self): return + def test_export_objects_anonymous(self): + return + def test_list_objects_with_constrained_permission(self): return @@ -962,6 +965,9 @@ class SubscriptionTestCase( login_url = reverse('login') self.assertRedirects(self.client.get(url), f'{login_url}?next={url}') + def test_export_objects_anonymous(self): + return + def test_list_objects_with_permission(self): return @@ -1070,6 +1076,9 @@ class NotificationTestCase( login_url = reverse('login') self.assertRedirects(self.client.get(url), f'{login_url}?next={url}') + def test_export_objects_anonymous(self): + return + def test_list_objects_with_permission(self): return diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 65e2a58ae..eef9e490b 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -181,7 +181,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): if request.GET['export'] == 'table': table = self.get_table(self.queryset, request, has_table_actions) columns = [name for name, _ in table.selected_columns] - delimiter = request.user.config.get('csv_delimiter') + delimiter = request.user.config.get('csv_delimiter') if request.user.is_authenticated else None return self.export_table(table, columns, delimiter=delimiter) # Render an ExportTemplate @@ -202,7 +202,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): # Fall back to default table/YAML export table = self.get_table(self.queryset, request, has_table_actions) - delimiter = request.user.config.get('csv_delimiter') + delimiter = request.user.config.get('csv_delimiter') if request.user.is_authenticated else None return self.export_table(table, delimiter=delimiter) # Render the objects table diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py index ac24c61d5..33c9ac25d 100644 --- a/netbox/utilities/testing/views.py +++ b/netbox/utilities/testing/views.py @@ -517,6 +517,31 @@ class ViewTestCases: self.assertHttpStatus(response, 200) self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8') + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], LOGIN_REQUIRED=False) + def test_export_objects_anonymous(self): + # Ensure we are logged out. + self.client.logout() + + # Some models (e.g. the users model) always require to be logged in, so we skip them here. + ct = ContentType.objects.get_for_model(self.model) + if (ct.app_label, ct.model) in settings.EXEMPT_EXCLUDE_MODELS: + return + + url = self._get_url('list') + + # Test default CSV (or sometimes YAML) export + response = self.client.get(f'{url}?export') + self.assertHttpStatus(response, 200) + if hasattr(self.model, 'to_yaml'): + self.assertEqual(response.get('Content-Type'), 'text/yaml') + else: + self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8') + + # Test table-based export + response = self.client.get(f'{url}?export=table') + self.assertHttpStatus(response, 200) + self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8') + class CreateMultipleObjectsViewTestCase(ModelViewTestCase): """ Create multiple instances using a single form. Expects the creation of three new instances by default. From c889e58bee0eb011227ea467ed9e5a7d2b20c816 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 06:46:14 +0000 Subject: [PATCH 45/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 67ffa3b98..18bba8370 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-14 06:30+0000\n" +"POT-Creation-Date: 2026-06-15 06:45+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -16781,7 +16781,7 @@ msgstr "" msgid "Selected" msgstr "" -#: netbox/utilities/testing/views.py:728 +#: netbox/utilities/testing/views.py:753 msgid "The test must define csv_update_data." msgstr "" From 8afbfc42d523ef90a7c0ee64ab97909ac800c617 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 15 Jun 2026 08:51:06 -0400 Subject: [PATCH 46/58] Fixes #22346: Return a clean error message & redirect on SSO auth failure (#22420) --- netbox/netbox/middleware.py | 13 +++++ netbox/netbox/settings.py | 8 +++ netbox/netbox/tests/test_authentication.py | 58 +++++++++++++++++++++- 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py index 4a36c2d98..a9ee93fc6 100644 --- a/netbox/netbox/middleware.py +++ b/netbox/netbox/middleware.py @@ -10,7 +10,9 @@ from django.db import ProgrammingError, connection from django.db.utils import InternalError from django.http import Http404, HttpResponseRedirect from django.middleware.common import CommonMiddleware as DjangoCommonMiddleware +from django.utils.translation import gettext_lazy as _ from django_prometheus import middleware +from social_django.middleware import SocialAuthExceptionMiddleware as SocialAuthExceptionMiddleware_ from netbox.config import clear_config, get_config from netbox.metrics import Metrics @@ -26,6 +28,7 @@ __all__ = ( 'PrometheusAfterMiddleware', 'PrometheusBeforeMiddleware', 'RemoteUserMiddleware', + 'SocialAuthExceptionMiddleware', ) @@ -286,3 +289,13 @@ class MaintenanceModeMiddleware: messages.error(request, error_message) return HttpResponseRedirect(request.path_info) return None + + +class SocialAuthExceptionMiddleware(SocialAuthExceptionMiddleware_): + """ + Subclass of python-social-auth's exception middleware which surfaces a generic, user-friendly + message rather than exposing the raw social_core exception text to (typically unauthenticated) + users when an SSO/SAML login fails. + """ + def get_message(self, request, exception): + return _("Single sign-on failed. Please try again or contact your administrator.") diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index e449d0688..cd5fb2e4e 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -516,6 +516,7 @@ MIDDLEWARE = [ 'netbox.middleware.RemoteUserMiddleware', 'netbox.middleware.CoreMiddleware', 'netbox.middleware.MaintenanceModeMiddleware', + 'netbox.middleware.SocialAuthExceptionMiddleware', ] if DEBUG: @@ -725,6 +726,13 @@ SOCIAL_AUTH_PIPELINE = ( 'social_core.pipeline.user.user_details', ) +# Redirect users back to the login page (surfacing the error via the messages framework) when an +# SSO/SAML authentication failure occurs, rather than raising an HTTP 500. Full exceptions are still +# raised when DEBUG is enabled. LOGIN_URL is an absolute path which respects BASE_PATH; the social +# auth middleware passes this value directly to an HttpResponseRedirect without reversing it. +SOCIAL_AUTH_LOGIN_ERROR_URL = LOGIN_URL +SOCIAL_AUTH_RAISE_EXCEPTIONS = DEBUG + # Load all SOCIAL_AUTH_* settings from the user configuration for param in dir(configuration): if param.startswith('SOCIAL_AUTH_'): diff --git a/netbox/netbox/tests/test_authentication.py b/netbox/netbox/tests/test_authentication.py index 55298cb2f..5221fe4b8 100644 --- a/netbox/netbox/tests/test_authentication.py +++ b/netbox/netbox/tests/test_authentication.py @@ -1,13 +1,16 @@ import datetime from django.conf import settings -from django.test import Client +from django.contrib.messages.storage.fallback import FallbackStorage +from django.test import Client, RequestFactory, SimpleTestCase from django.test.utils import override_settings from django.urls import reverse from rest_framework.test import APIClient +from social_core.exceptions import AuthFailed from core.models import ObjectType from dcim.models import Rack, Site +from netbox.middleware import SocialAuthExceptionMiddleware from users.constants import TOKEN_PREFIX from users.models import Group, ObjectPermission, Token, User from utilities.testing import TestCase @@ -697,3 +700,56 @@ class ObjectPermissionAPIViewTestCase(TestCase): url = reverse('dcim-api:rack-detail', kwargs={'pk': self.racks[0].pk}) response = self.client.delete(url, format='json', **self.header) self.assertEqual(response.status_code, 204) + + +class SocialAuthExceptionMiddlewareTestCase(SimpleTestCase): + """ + Verify that SSO/SAML authentication failures are surfaced as a login-page message rather than + bubbling up as an HTTP 500 (see #22346). + """ + GENERIC_MESSAGE = "Single sign-on failed. Please try again or contact your administrator." + + class FakeStrategy: + # Mirror social_core's DjangoStrategy.setting(), which reads SOCIAL_AUTH_ from Django + # settings. This ensures the test exercises the real configured values (e.g. + # SOCIAL_AUTH_LOGIN_ERROR_URL) rather than hardcoded stand-ins. + def setting(self, name, default=None, backend=None): + return getattr(settings, f'SOCIAL_AUTH_{name}', default) + + class FakeBackend: + name = 'saml' + + def setUp(self): + self.factory = RequestFactory() + self.middleware = SocialAuthExceptionMiddleware(lambda request: None) + + def _make_request(self): + request = self.factory.get('/') + request.social_strategy = self.FakeStrategy() + request.backend = self.FakeBackend() + # Attach message storage (normally provided by MessageMiddleware) + setattr(request, 'session', {}) + request._messages = FallbackStorage(request) + return request + + def test_generic_message(self): + """ + The raw exception text should never be surfaced to the user. + """ + request = self._make_request() + exception = AuthFailed(self.FakeBackend(), 'raw internal SAML detail') + self.assertEqual(self.middleware.get_message(request, exception), self.GENERIC_MESSAGE) + + def test_redirect_on_failure(self): + """ + A SocialAuthBaseException should redirect to the login page with the generic message set. + """ + request = self._make_request() + exception = AuthFailed(self.FakeBackend(), 'raw internal SAML detail') + response = self.middleware.process_exception(request, exception) + + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, settings.SOCIAL_AUTH_LOGIN_ERROR_URL) + self.assertEqual(response.url, settings.LOGIN_URL) + messages = [str(m) for m in request._messages] + self.assertEqual(messages, [self.GENERIC_MESSAGE]) From d7de86368172cd78b06479c57da32aafd22d66f6 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Mon, 15 Jun 2026 15:22:58 +0200 Subject: [PATCH 47/58] Closes #17598: Add bulk creation for VLANs (#22377) --- docs/models/ipam/vlan.md | 6 + netbox/ipam/forms/bulk_create.py | 18 +- netbox/ipam/forms/model_forms.py | 21 +++ netbox/ipam/models/vlans.py | 2 +- netbox/ipam/tests/test_forms.py | 30 ++- netbox/ipam/tests/test_models.py | 7 + netbox/ipam/tests/test_views.py | 173 ++++++++++++++++++ netbox/ipam/views.py | 10 + netbox/netbox/views/generic/bulk_views.py | 102 ++++++++++- .../templates/ipam/inc/vlan_edit_header.html | 23 +++ netbox/templates/ipam/vlan_bulk_add.html | 5 + netbox/templates/ipam/vlan_edit.html | 4 + 12 files changed, 388 insertions(+), 13 deletions(-) create mode 100644 netbox/templates/ipam/inc/vlan_edit_header.html create mode 100644 netbox/templates/ipam/vlan_bulk_add.html diff --git a/docs/models/ipam/vlan.md b/docs/models/ipam/vlan.md index 58fc9f551..545ea6553 100644 --- a/docs/models/ipam/vlan.md +++ b/docs/models/ipam/vlan.md @@ -2,6 +2,12 @@ A Virtual LAN (VLAN) represents an isolated layer two domain, identified by a name and a numeric ID (1-4094) as defined in [IEEE 802.1Q](https://en.wikipedia.org/wiki/IEEE_802.1Q). VLANs are arranged into [VLAN groups](./vlangroup.md) to define scope and to enforce uniqueness. +## Bulk Creation + +Multiple VLANs can be created at once by selecting the "Bulk Create" tab on the VLAN creation form. Enter the desired VLAN IDs and/or ID ranges as a comma-separated list (e.g. `100,200-210,4000-4010`). The string `{vid}` may be embedded in the name field as a placeholder for each VLAN's ID; for example, `VLAN-{vid}` yields `VLAN-100`, `VLAN-200`, and so on. All other attributes (status, role, tenant, etc.) are applied to every new VLAN. + +The operation is atomic: if any VLAN fails validation (for example, a VLAN ID falling outside the assigned group's permitted ranges), no VLANs are created. + ## Fields ### ID diff --git a/netbox/ipam/forms/bulk_create.py b/netbox/ipam/forms/bulk_create.py index 763dbac9c..da6bd0fdb 100644 --- a/netbox/ipam/forms/bulk_create.py +++ b/netbox/ipam/forms/bulk_create.py @@ -1,10 +1,12 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from utilities.forms.fields import ExpandableIPNetworkField +from ipam.constants import VLAN_VID_MAX, VLAN_VID_MIN +from utilities.forms.fields import ExpandableIPNetworkField, NumericArrayField __all__ = ( 'IPNetworkBulkCreateForm', + 'VLANIDBulkCreateForm', ) @@ -15,3 +17,17 @@ class IPNetworkBulkCreateForm(forms.Form): pattern = ExpandableIPNetworkField( label=_('Pattern') ) + + +class VLANIDBulkCreateForm(forms.Form): + pattern = NumericArrayField( + base_field=forms.IntegerField( + min_value=VLAN_VID_MIN, + max_value=VLAN_VID_MAX + ), + label=_('VLAN IDs'), + help_text=_( + 'Enter VLAN IDs and ranges separated by commas. ' + 'Example: 100,200-210,3100-3299' + ) + ) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index bffe7a1eb..d2f2a2ad6 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -45,6 +45,7 @@ __all__ = ( 'ServiceCreateForm', 'ServiceForm', 'ServiceTemplateForm', + 'VLANBulkAddForm', 'VLANForm', 'VLANGroupForm', 'VLANTranslationPolicyForm', @@ -727,6 +728,26 @@ class VLANForm(TenancyForm, PrimaryModelForm): ] +class VLANBulkAddForm(VLANForm): + """ + Subclass of VLANForm for bulk creation. + + The VID field is inherited but excluded from the visible fieldsets, as it is + populated programmatically by BulkCreateView from the expanded pattern. + """ + fieldsets = ( + FieldSet('group', 'site', 'name', 'status', 'role', 'description', 'tags', name=_('VLAN')), + FieldSet('qinq_role', 'qinq_svlan', name=_('Q-in-Q/802.1ad')), + FieldSet('tenant_group', 'tenant', name=_('Tenancy')), + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['name'].help_text = _( + 'Use {vid} as a placeholder for the VLAN ID. Example: VLAN-{vid}.' + ) + + class VLANTranslationPolicyForm(PrimaryModelForm): fieldsets = ( diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 2ed3512df..40f56f87f 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -330,7 +330,7 @@ class VLAN(PrimaryModel): ) # Check that the VLAN ID is permitted in the assigned group (if any) - if self.group: + if self.group and self.vid is not None: if not any([self.vid in r for r in self.group.vid_ranges]): raise ValidationError({ 'vid': _( diff --git a/netbox/ipam/tests/test_forms.py b/netbox/ipam/tests/test_forms.py index 621afc6a8..33bdfb627 100644 --- a/netbox/ipam/tests/test_forms.py +++ b/netbox/ipam/tests/test_forms.py @@ -3,7 +3,7 @@ from django.test import TestCase from dcim.constants import InterfaceTypeChoices from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Region, Site, SiteGroup -from ipam.forms import PrefixForm +from ipam.forms import PrefixForm, VLANIDBulkCreateForm from ipam.forms.bulk_import import IPAddressImportForm @@ -96,3 +96,31 @@ class IPAddressImportFormTestCase(TestCase): self.device.refresh_from_db() self.assertEqual(self.device.oob_ip, ip1, "OOB IP was incorrectly cleared by a row with is_oob=False") + + +class VLANFormTestCase(TestCase): + + def test_bulk_create_valid_patterns(self): + """Single values, ranges, and combinations expand to sorted, deduplicated VLAN IDs.""" + cases = ( + ('100', [100]), + ('5,10,20', [5, 10, 20]), + ('10-20', list(range(10, 21))), + ('1,10-20,300-305', [1, *range(10, 21), *range(300, 306)]), + (' 5 , 7 - 9 ', [5, 7, 8, 9]), + ('5,5,4-6', [4, 5, 6]), + ) + for pattern, expected in cases: + with self.subTest(pattern=pattern): + form = VLANIDBulkCreateForm({'pattern': pattern}) + self.assertTrue(form.is_valid(), form.errors) + self.assertEqual(form.cleaned_data['pattern'], expected) + + def test_bulk_create_invalid_patterns(self): + """Malformed, descending, or out-of-range patterns are rejected with an error on the pattern field.""" + cases = ('', 'abc', '10,abc', '20-10', '10-', '5,', '-5', '0', '4095') + for pattern in cases: + with self.subTest(pattern=pattern): + form = VLANIDBulkCreateForm({'pattern': pattern}) + self.assertFalse(form.is_valid()) + self.assertIn('pattern', form.errors) diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index b9a99a445..b76a7205b 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -1808,6 +1808,13 @@ class VLANTestCase(TestCase): with self.assertRaises(ValidationError): vlan.full_clean() + def test_vlan_group_vid_validation_with_null_vid(self): + """A missing VID on a grouped VLAN raises a ValidationError, not a TypeError.""" + group = VLANGroup.objects.create(name='VLAN Group 1', slug='vlan-group-1') + vlan = VLAN(name='VLAN X', vid=None, group=group) + with self.assertRaises(ValidationError): + vlan.full_clean() + class PrefixGetChildIPsTestCase(TestCase): @classmethod diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 5505d2a5c..9bf73038f 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -1,6 +1,7 @@ import datetime from django.contrib.contenttypes.models import ContentType +from django.db.backends.postgresql.psycopg_any import NumericRange from django.test import RequestFactory from django.urls import reverse from netaddr import IPNetwork @@ -1501,6 +1502,178 @@ class VLANTestCase(ViewTestCases.PrimaryObjectViewTestCase): 'description': 'New description', } + def test_bulk_add_vlans(self): + self.add_permissions('ipam.add_vlan') + + group = VLANGroup.objects.get(name='VLAN Group 1') + initial_count = VLAN.objects.count() + expected_vids = (110, 120, 121, 122) + + form_data = { + 'pattern': '110,120-122', + 'group': group.pk, + 'name': 'Pool-{vid}', + 'status': VLANStatusChoices.STATUS_RESERVED, + } + + response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data) + + self.assertHttpStatus(response, 302) + self.assertEqual(VLAN.objects.count(), initial_count + len(expected_vids)) + + for vid in expected_vids: + self.assertTrue( + VLAN.objects.filter( + group=group, + vid=vid, + name=f'Pool-{vid}' + ).exists() + ) + + def test_bulk_add_vlans_rolls_back_on_duplicate_name(self): + self.add_permissions('ipam.add_vlan') + + group = VLANGroup.objects.get(name='VLAN Group 1') + initial_count = VLAN.objects.count() + + form_data = { + 'pattern': '110-112', + 'group': group.pk, + 'name': 'Duplicate name', + 'status': VLANStatusChoices.STATUS_RESERVED, + } + + response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data) + + self.assertHttpStatus(response, 200) + self.assertEqual(VLAN.objects.count(), initial_count) + self.assertFalse(VLAN.objects.filter(group=group, vid=110).exists()) + + def test_bulk_add_vlans_rolls_back_when_any_id_outside_group_range(self): + self.add_permissions('ipam.add_vlan') + + group = VLANGroup.objects.create( + name='Restricted VLAN Group', + slug='restricted-vlan-group', + vid_ranges=[NumericRange(200, 204)] # Valid VIDs: 200-203 + ) + initial_count = VLAN.objects.count() + + form_data = { + 'pattern': '200-203,500', + 'group': group.pk, + 'name': 'Restricted-{vid}', + 'status': VLANStatusChoices.STATUS_RESERVED, + } + + response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data) + + self.assertHttpStatus(response, 200) + self.assertEqual(VLAN.objects.count(), initial_count) + self.assertFalse(VLAN.objects.filter(group=group, vid=200).exists()) + self.assertFalse(VLAN.objects.filter(group=group, vid=203).exists()) + self.assertFalse(VLAN.objects.filter(group=group, vid=500).exists()) + + def test_bulk_add_vlans_pattern_shapes(self): + """Single values, multiple values, ranges, and combinations create the expected VLANs.""" + self.add_permissions('ipam.add_vlan') + # The combination runs against a second group: subTests share one transaction, and VIDs + # 10 & 20 would otherwise collide with the multiple-values case via the (group, vid) constraint. + cases = ( + ('500', (500,), 'VLAN Group 1'), + ('5,10,20', (5, 10, 20), 'VLAN Group 1'), + ('600-605', tuple(range(600, 606)), 'VLAN Group 1'), + ('1,10-20,300-305', (1, *range(10, 21), *range(300, 306)), 'VLAN Group 2'), + ) + for pattern, expected_vids, group_name in cases: + with self.subTest(pattern=pattern): + group = VLANGroup.objects.get(name=group_name) + initial_count = VLAN.objects.count() + form_data = { + 'pattern': pattern, + 'group': group.pk, + 'name': 'Pool-{vid}', + 'status': VLANStatusChoices.STATUS_ACTIVE, + } + response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data) + self.assertHttpStatus(response, 302) + self.assertEqual(VLAN.objects.count(), initial_count + len(expected_vids)) + for vid in expected_vids: + self.assertTrue(VLAN.objects.filter(group=group, vid=vid, name=f'Pool-{vid}').exists()) + + def test_bulk_add_vlans_invalid_pattern(self): + """An invalid pattern re-renders the form with a pattern error and creates nothing.""" + self.add_permissions('ipam.add_vlan') + initial_count = VLAN.objects.count() + + for pattern in ('abc', '20-10', '0', '4095', '10-'): + with self.subTest(pattern=pattern): + form_data = { + 'pattern': pattern, + 'name': 'Pool-{vid}', + 'status': VLANStatusChoices.STATUS_ACTIVE, + } + response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data) + self.assertHttpStatus(response, 200) + self.assertIn('pattern', response.context['form'].errors) + self.assertEqual(VLAN.objects.count(), initial_count) + + def test_bulk_add_vlans_static_name_without_group(self): + """A static name (no {vid} placeholder) is permitted across VLANs not assigned to a group.""" + self.add_permissions('ipam.add_vlan') + initial_count = VLAN.objects.count() + + form_data = { + 'pattern': '710-712', + 'name': 'Same name', + 'status': VLANStatusChoices.STATUS_ACTIVE, + } + response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data) + + self.assertHttpStatus(response, 302) + self.assertEqual(VLAN.objects.count(), initial_count + 3) + self.assertEqual(VLAN.objects.filter(name='Same name').count(), 3) + + def test_bulk_add_vlans_rolls_back_on_constrained_permission(self): + """Bulk creation rolls back when a generated VLAN falls outside the user's add constraints.""" + obj_perm = ObjectPermission( + name='Test permission', + actions=['add'], + constraints={'vid__lt': 120} + ) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ObjectType.objects.get_for_model(VLAN)) + + initial_count = VLAN.objects.count() + form_data = { + 'pattern': '110,120-122', + 'name': 'Pool-{vid}', + 'status': VLANStatusChoices.STATUS_ACTIVE, + } + response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data) + + self.assertHttpStatus(response, 200) + self.assertEqual(VLAN.objects.count(), initial_count) + self.assertTrue(response.context['form'].non_field_errors()) + + def test_bulk_add_vlans_propagates_field_errors(self): + """A per-object validation error on a non-pattern field is reported on the bulk-create form.""" + self.add_permissions('ipam.add_vlan') + initial_count = VLAN.objects.count() + + form_data = { + 'pattern': '800', + 'name': 'Pool-{vid}', + 'status': VLANStatusChoices.STATUS_ACTIVE, + 'qinq_role': VLANQinQRoleChoices.ROLE_CUSTOMER, # Requires an SVLAN + } + response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data) + + self.assertHttpStatus(response, 200) + self.assertEqual(VLAN.objects.count(), initial_count) + self.assertTrue(response.context['form'].non_field_errors()) + class VLANTranslationPolicyTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = VLANTranslationPolicy diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 98560cc8b..3c57b8598 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -1793,6 +1793,16 @@ class VLANDeleteView(generic.ObjectDeleteView): queryset = VLAN.objects.all() +@register_model_view(VLAN, 'bulk_add', path='bulk-add', detail=False) +class VLANBulkCreateView(generic.BulkCreateView): + queryset = VLAN.objects.all() + form = forms.VLANIDBulkCreateForm + model_form = forms.VLANBulkAddForm + pattern_target = 'vid' + pattern_template_fields = ('name',) + template_name = 'ipam/vlan_bulk_add.html' + + @register_model_view(VLAN, 'bulk_import', path='import', detail=False) class VLANBulkImportView(generic.BulkImportView): queryset = VLAN.objects.all() diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index eef9e490b..b6578b1eb 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -27,7 +27,7 @@ from netbox.forms.bulk_rename import NetBoxModelBulkRenameForm from netbox.models.features import ChangeLoggingMixin from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename from utilities.error_handlers import handle_protectederror -from utilities.exceptions import AbortRequest, PermissionsViolation +from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation from utilities.export import TableExport, stream_table_csv_response from utilities.forms import BulkDeleteForm, BulkRenameForm, restrict_form_fields from utilities.forms.bulk_import import BulkImportForm @@ -245,11 +245,96 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView): form = None model_form = None pattern_target = '' + pattern_template_fields = () htmx_template_name = 'htmx/bulk_add_form.html' def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'add') + def get_pattern_context(self, value): + """ + Return a context mapping for substituting the generated pattern value into + model form fields. + + By default, the field named by ``pattern_target`` is supported as a + placeholder, e.g. ``{vid}``. + """ + if not self.pattern_target: + return {} + + return { + self.pattern_target: str(value), + } + + def render_pattern_template(self, template, value): + """ + Replace pattern placeholders in a single form field value. + """ + rendered = str(template) + + for key, replacement in self.get_pattern_context(value).items(): + rendered = rendered.replace(f'{{{key}}}', replacement) + + return rendered + + def apply_pattern_template_fields(self, data, value): + """ + Apply the generated pattern value to any configured template fields. + """ + for field_name in self.pattern_template_fields: + if field_name not in data: + continue + + # QueryDict values may be multi-valued; preserve that behavior. + if hasattr(data, 'getlist') and hasattr(data, 'setlist'): + data.setlist(field_name, [ + self.render_pattern_template(field_value, value) + for field_value in data.getlist(field_name) + ]) + else: + data[field_name] = self.render_pattern_template(data[field_name], value) + + return data + + def get_model_form_data(self, form, request, value): + """ + Return the submitted data to use when instantiating the model form for a + single generated pattern value. + """ + data = request.POST.copy() + data[self.pattern_target] = value + + return self.apply_pattern_template_fields(data, value) + + def add_model_form_errors(self, form, model_form, value): + """ + Copy validation errors from the generated object's model form back onto + the pattern form for display. + """ + errors = model_form.errors.as_data() + + if errors.get(self.pattern_target): + form.add_error('pattern', errors.pop(self.pattern_target)) + + for field_name, field_errors in errors.items(): + if field_name == '__all__': + field_label = _('General') + elif field_name in model_form.fields: + field_label = model_form.fields[field_name].label + else: + field_label = field_name + + for error in field_errors: + for message in error.messages: + form.add_error( + None, + _('{value}: {field}: {error}').format( + value=value, + field=field_label, + error=message, + ) + ) + def _create_objects(self, form, request): new_objects = [] @@ -258,8 +343,7 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView): # Reinstantiate the model form each time to avoid overwriting the same instance. Use a mutable # copy of the POST QueryDict so that we can update the target field value. - model_form = self.model_form(request.POST.copy()) - model_form.data[self.pattern_target] = value + model_form = self.model_form(self.get_model_form_data(form, request, value)) # Validate each new object independently. if model_form.is_valid(): @@ -267,12 +351,10 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView): obj = model_form.save() new_objects.append(obj) else: - # Copy any errors on the pattern target field to the pattern form. - errors = model_form.errors.as_data() - if errors.get(self.pattern_target): - form.add_error('pattern', errors[self.pattern_target]) - # Raise an IntegrityError to break the for loop and abort the transaction. - raise IntegrityError() + self.add_model_form_errors(form, model_form, value) + + # Abort the transaction and break out of the loop. + raise AbortTransaction() return new_objects @@ -343,7 +425,7 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView): return redirect(request.path) return redirect(self.get_return_url(request)) - except IntegrityError: + except (AbortTransaction, IntegrityError): pass except (AbortRequest, PermissionsViolation) as e: diff --git a/netbox/templates/ipam/inc/vlan_edit_header.html b/netbox/templates/ipam/inc/vlan_edit_header.html new file mode 100644 index 000000000..ca87d433f --- /dev/null +++ b/netbox/templates/ipam/inc/vlan_edit_header.html @@ -0,0 +1,23 @@ +{% load helpers %} +{% load i18n %} + + diff --git a/netbox/templates/ipam/vlan_bulk_add.html b/netbox/templates/ipam/vlan_bulk_add.html new file mode 100644 index 000000000..4a088d4de --- /dev/null +++ b/netbox/templates/ipam/vlan_bulk_add.html @@ -0,0 +1,5 @@ +{% extends 'generic/bulk_add.html' %} + +{% block tabs %} + {% include 'ipam/inc/vlan_edit_header.html' with active_tab='bulk_add' %} +{% endblock tabs %} diff --git a/netbox/templates/ipam/vlan_edit.html b/netbox/templates/ipam/vlan_edit.html index 7c20c801b..850de8b9c 100644 --- a/netbox/templates/ipam/vlan_edit.html +++ b/netbox/templates/ipam/vlan_edit.html @@ -4,6 +4,10 @@ {% load helpers %} {% load i18n %} +{% block tabs %} + {% include 'ipam/inc/vlan_edit_header.html' with active_tab='add' %} +{% endblock tabs %} + {% block form %} {% for field in form.hidden_fields %} {{ field }} From eaed2a7f8e4ed58072d839946a503bf8d88a39f5 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Mon, 15 Jun 2026 15:34:22 +0200 Subject: [PATCH 48/58] refactor(graphql): Use factories for schema extension initialization Change `get_schema_extensions()` to return extension factories instead of instances. This defers extension initialization and prevents stale references to settings captured at import time. Lambdas capture settings values when extensions are constructed, and tests now instantiate extensions from factories to verify configuration. Fixes #22451 --- netbox/netbox/graphql/schema.py | 18 ++++++++++++------ netbox/netbox/tests/test_graphql.py | 13 ++++++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/netbox/netbox/graphql/schema.py b/netbox/netbox/graphql/schema.py index 1cce4a7dc..6b432050f 100644 --- a/netbox/netbox/graphql/schema.py +++ b/netbox/netbox/graphql/schema.py @@ -1,3 +1,5 @@ +from collections.abc import Callable + import strawberry from django.conf import settings from strawberry.extensions import MaxAliasesLimiter, QueryDepthLimiter, SchemaExtension @@ -18,6 +20,8 @@ from wireless.graphql.schema import WirelessQuery from .scalars import BigInt, BigIntScalar +SchemaExtensionFactory = type[SchemaExtension] | Callable[[], SchemaExtension] + @strawberry.type class Query( @@ -36,14 +40,16 @@ class Query( pass -def get_schema_extensions() -> list[SchemaExtension]: - extensions: list[SchemaExtension] = [ - DjangoOptimizerExtension(prefetch_custom_queryset=True), - MaxAliasesLimiter(max_alias_count=settings.GRAPHQL_MAX_ALIASES), - ] +def get_schema_extensions() -> list[SchemaExtensionFactory]: + max_aliases = settings.GRAPHQL_MAX_ALIASES max_depth = settings.GRAPHQL_MAX_QUERY_DEPTH + + extensions: list[SchemaExtensionFactory] = [ + lambda: DjangoOptimizerExtension(prefetch_custom_queryset=True), + lambda: MaxAliasesLimiter(max_alias_count=max_aliases), + ] if max_depth and max_depth > 0: - extensions.append(QueryDepthLimiter(max_depth=max_depth)) + extensions.append(lambda: QueryDepthLimiter(max_depth=max_depth)) return extensions diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index f960890f7..e14d9c7ff 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -19,6 +19,9 @@ from utilities.testing import APITestCase, TestCase, disable_warnings class GraphQLTestCase(TestCase): + def _schema_extension_instances(self): + return [factory() for factory in get_schema_extensions()] + @override_settings(GRAPHQL_ENABLED=False) def test_graphql_enabled(self): """ @@ -32,21 +35,21 @@ class GraphQLTestCase(TestCase): """ QueryDepthLimiter should not be installed when GRAPHQL_MAX_QUERY_DEPTH is unset. """ - self.assertFalse(any(isinstance(ext, QueryDepthLimiter) for ext in get_schema_extensions())) + self.assertFalse(any(isinstance(ext, QueryDepthLimiter) for ext in self._schema_extension_instances())) @override_settings(GRAPHQL_MAX_QUERY_DEPTH=0) def test_graphql_max_query_depth_disabled_when_zero(self): """ QueryDepthLimiter should not be installed when GRAPHQL_MAX_QUERY_DEPTH is zero. """ - self.assertFalse(any(isinstance(ext, QueryDepthLimiter) for ext in get_schema_extensions())) + self.assertFalse(any(isinstance(ext, QueryDepthLimiter) for ext in self._schema_extension_instances())) @override_settings(GRAPHQL_MAX_QUERY_DEPTH=-1) def test_graphql_max_query_depth_disabled_when_negative(self): """ QueryDepthLimiter should not be installed when GRAPHQL_MAX_QUERY_DEPTH is negative. """ - self.assertFalse(any(isinstance(ext, QueryDepthLimiter) for ext in get_schema_extensions())) + self.assertFalse(any(isinstance(ext, QueryDepthLimiter) for ext in self._schema_extension_instances())) @override_settings(GRAPHQL_MAX_QUERY_DEPTH=3) def test_graphql_max_query_depth_enforced(self): @@ -54,9 +57,9 @@ class GraphQLTestCase(TestCase): Queries exceeding GRAPHQL_MAX_QUERY_DEPTH should be rejected. """ extensions = get_schema_extensions() - self.assertTrue(any(isinstance(ext, QueryDepthLimiter) for ext in extensions)) + self.assertTrue(any(isinstance(ext, QueryDepthLimiter) for ext in self._schema_extension_instances())) - # Build a temporary schema with the configured extensions and execute a deep query + # Build a temporary schema with the configured extension factories and execute a deep query test_schema = strawberry.Schema( query=Query, config=StrawberryConfig(auto_camel_case=False, scalar_map={BigInt: BigIntScalar}), From b7de62610f067e681011bdd2aaf62241eb59d95d Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Mon, 15 Jun 2026 16:21:46 +0200 Subject: [PATCH 49/58] Fixes #22395: Remove unused save() override on ManagedFileForm The method wrote uploaded files to disk via a raw open(), but no code path reached it. Its only subclass, ScriptFileForm, overrode save() to write through django-storages and explicitly skipped the base via super(ManagedFileForm, self).save(). With the override gone, that call simplifies back to a plain super().save(). A leftover from #18680, which moved both upload paths onto django-storages but left the form-level write in place. --- netbox/core/forms/model_forms.py | 9 --------- netbox/extras/forms/scripts.py | 3 +-- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/netbox/core/forms/model_forms.py b/netbox/core/forms/model_forms.py index 074ea8498..aca3d9fd3 100644 --- a/netbox/core/forms/model_forms.py +++ b/netbox/core/forms/model_forms.py @@ -114,15 +114,6 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm): return self.cleaned_data - def save(self, *args, **kwargs): - # If a file was uploaded, save it to disk - if self.cleaned_data['upload_file']: - self.instance.file_path = self.cleaned_data['upload_file'].name - with open(self.instance.full_path, 'wb+') as new_file: - new_file.write(self.cleaned_data['upload_file'].read()) - - return super().save(*args, **kwargs) - class ConfigFormMetaclass(forms.models.ModelFormMetaclass): diff --git a/netbox/extras/forms/scripts.py b/netbox/extras/forms/scripts.py index b646c397d..2aa8f4786 100644 --- a/netbox/extras/forms/scripts.py +++ b/netbox/extras/forms/scripts.py @@ -112,5 +112,4 @@ class ScriptFileForm(ManagedFileForm): data = self.cleaned_data['upload_file'] storage.save(filename, data) - # need to skip ManagedFileForm save method - return super(ManagedFileForm, self).save(*args, **kwargs) + return super().save(*args, **kwargs) From 9bfdea4787071de8b8fb509d377e513c3bab2684 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 15 Jun 2026 16:24:10 -0400 Subject: [PATCH 50/58] Fixes #22454: Fix serialization of decimal custom field values (#22460) --- netbox/extras/models/customfields.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index a076e2538..31d417564 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -461,6 +461,8 @@ class CustomField(CloningMixin, ExportTemplatesMixin, OwnerMixin, ChangeLoggedMo """ if value is None: return value + if self.type == CustomFieldTypeChoices.TYPE_DECIMAL: + return float(value) if self.type == CustomFieldTypeChoices.TYPE_DATE and type(value) is date: return value.isoformat() if self.type == CustomFieldTypeChoices.TYPE_DATETIME and type(value) is datetime: From 1264797fa6fd3df5cceb2b228c14335bd51f01e5 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 06:47:17 +0000 Subject: [PATCH 51/58] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 380 ++++++++++--------- 1 file changed, 204 insertions(+), 176 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 18bba8370..2c891afbd 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-15 06:45+0000\n" +"POT-Creation-Date: 2026-06-16 06:46+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -190,11 +190,11 @@ msgstr "" #: netbox/extras/filtersets.py:732 netbox/ipam/forms/bulk_edit.py:419 #: netbox/ipam/forms/bulk_import.py:515 netbox/ipam/forms/filtersets.py:176 #: netbox/ipam/forms/filtersets.py:256 netbox/ipam/forms/filtersets.py:487 -#: netbox/ipam/forms/filtersets.py:594 netbox/ipam/forms/model_forms.py:693 +#: netbox/ipam/forms/filtersets.py:594 netbox/ipam/forms/model_forms.py:694 #: netbox/ipam/tables/vlans.py:94 #: netbox/templates/dcim/inc/cable_termination.html:8 #: netbox/templates/dcim/inc/cable_termination.html:36 -#: netbox/templates/ipam/vlan_edit.html:52 +#: netbox/templates/ipam/vlan_edit.html:56 #: netbox/virtualization/forms/bulk_edit.py:127 #: netbox/virtualization/forms/bulk_import.py:62 #: netbox/virtualization/forms/bulk_import.py:124 @@ -223,7 +223,7 @@ msgid "ASN (ID)" msgstr "" #: netbox/circuits/filtersets.py:85 netbox/circuits/forms/filtersets.py:42 -#: netbox/ipam/forms/model_forms.py:169 netbox/ipam/models/asns.py:137 +#: netbox/ipam/forms/model_forms.py:170 netbox/ipam/models/asns.py:137 #: netbox/ipam/models/asns.py:163 netbox/ipam/tables/asn.py:51 msgid "ASN" msgstr "" @@ -301,7 +301,7 @@ msgstr "" #: netbox/extras/filtersets.py:503 netbox/extras/filtersets.py:560 #: netbox/extras/filtersets.py:621 netbox/extras/filtersets.py:660 #: netbox/extras/filtersets.py:693 netbox/extras/filtersets.py:867 -#: netbox/ipam/forms/model_forms.py:514 netbox/netbox/filtersets.py:302 +#: netbox/ipam/forms/model_forms.py:515 netbox/netbox/filtersets.py:302 #: netbox/netbox/forms/filtersets.py:32 netbox/netbox/forms/search.py:20 #: netbox/templates/htmx/object_selector.html:28 #: netbox/templates/inc/filter_list.html:43 @@ -560,7 +560,7 @@ msgstr "" #: netbox/ipam/forms/bulk_import.py:305 netbox/ipam/forms/bulk_import.py:536 #: netbox/ipam/forms/filtersets.py:239 netbox/ipam/forms/filtersets.py:318 #: netbox/ipam/forms/filtersets.py:407 netbox/ipam/forms/filtersets.py:606 -#: netbox/ipam/forms/model_forms.py:533 netbox/ipam/tables/ip.py:187 +#: netbox/ipam/forms/model_forms.py:534 netbox/ipam/tables/ip.py:187 #: netbox/ipam/tables/ip.py:266 netbox/ipam/tables/ip.py:319 #: netbox/ipam/tables/ip.py:394 netbox/ipam/tables/ip.py:421 #: netbox/ipam/tables/vlans.py:102 netbox/templates/core/rq_task.html:81 @@ -739,16 +739,16 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:170 netbox/dcim/forms/model_forms.py:226 #: netbox/dcim/forms/model_forms.py:330 netbox/dcim/forms/model_forms.py:387 #: netbox/dcim/forms/model_forms.py:945 netbox/dcim/forms/model_forms.py:1961 -#: netbox/ipam/forms/bulk_edit.py:388 netbox/ipam/forms/model_forms.py:71 -#: netbox/ipam/forms/model_forms.py:88 netbox/ipam/forms/model_forms.py:119 -#: netbox/ipam/forms/model_forms.py:140 netbox/ipam/forms/model_forms.py:170 -#: netbox/ipam/forms/model_forms.py:234 netbox/ipam/forms/model_forms.py:266 -#: netbox/ipam/forms/model_forms.py:288 netbox/ipam/forms/model_forms.py:346 -#: netbox/ipam/forms/model_forms.py:495 netbox/ipam/forms/model_forms.py:644 -#: netbox/netbox/navigation/menu.py:27 +#: netbox/ipam/forms/bulk_edit.py:388 netbox/ipam/forms/model_forms.py:72 +#: netbox/ipam/forms/model_forms.py:89 netbox/ipam/forms/model_forms.py:120 +#: netbox/ipam/forms/model_forms.py:141 netbox/ipam/forms/model_forms.py:171 +#: netbox/ipam/forms/model_forms.py:235 netbox/ipam/forms/model_forms.py:267 +#: netbox/ipam/forms/model_forms.py:289 netbox/ipam/forms/model_forms.py:347 +#: netbox/ipam/forms/model_forms.py:496 netbox/ipam/forms/model_forms.py:645 +#: netbox/ipam/forms/model_forms.py:741 netbox/netbox/navigation/menu.py:27 #: netbox/templates/dcim/device_edit.html:87 #: netbox/templates/dcim/htmx/cable_edit.html:76 -#: netbox/templates/ipam/vlan_edit.html:34 +#: netbox/templates/ipam/vlan_edit.html:38 #: netbox/virtualization/forms/model_forms.py:78 #: netbox/virtualization/forms/model_forms.py:267 #: netbox/vpn/forms/bulk_edit.py:66 netbox/vpn/forms/filtersets.py:52 @@ -879,9 +879,9 @@ msgstr "" #: netbox/ipam/forms/bulk_import.py:541 netbox/ipam/forms/filtersets.py:166 #: netbox/ipam/forms/filtersets.py:267 netbox/ipam/forms/filtersets.py:326 #: netbox/ipam/forms/filtersets.py:412 netbox/ipam/forms/filtersets.py:614 -#: netbox/ipam/forms/model_forms.py:158 netbox/ipam/forms/model_forms.py:196 -#: netbox/ipam/forms/model_forms.py:222 netbox/ipam/forms/model_forms.py:277 -#: netbox/ipam/forms/model_forms.py:708 netbox/ipam/tables/asn.py:75 +#: netbox/ipam/forms/model_forms.py:159 netbox/ipam/forms/model_forms.py:197 +#: netbox/ipam/forms/model_forms.py:223 netbox/ipam/forms/model_forms.py:278 +#: netbox/ipam/forms/model_forms.py:709 netbox/ipam/tables/asn.py:75 #: netbox/ipam/tables/ip.py:213 netbox/ipam/tables/ip.py:270 #: netbox/ipam/tables/ip.py:323 netbox/ipam/tables/vlans.py:106 #: netbox/templates/dcim/inc/panels/inventory_items.html:20 @@ -989,7 +989,7 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:1982 netbox/dcim/tables/connections.py:66 #: netbox/dcim/tables/devices.py:1229 netbox/dcim/views.py:3571 #: netbox/dcim/views.py:3675 netbox/ipam/forms/bulk_import.py:330 -#: netbox/ipam/forms/model_forms.py:307 netbox/ipam/forms/model_forms.py:316 +#: netbox/ipam/forms/model_forms.py:308 netbox/ipam/forms/model_forms.py:317 #: netbox/ipam/tables/fhrp.py:61 netbox/ipam/tables/ip.py:328 #: netbox/ipam/tables/vlans.py:149 #: netbox/templates/circuits/circuit_termination/attrs/connection.html:40 @@ -1087,7 +1087,7 @@ msgstr "" #: netbox/templates/dcim/htmx/cable_edit.html:84 #: netbox/templates/dcim/virtualchassis_edit.html:39 #: netbox/templates/generic/bulk_edit.html:65 -#: netbox/templates/htmx/form.html:28 netbox/templates/ipam/vlan_edit.html:70 +#: netbox/templates/htmx/form.html:28 netbox/templates/ipam/vlan_edit.html:74 #: netbox/tenancy/forms/filtersets.py:41 netbox/tenancy/forms/filtersets.py:56 #: netbox/tenancy/forms/filtersets.py:77 netbox/tenancy/forms/filtersets.py:91 #: netbox/tenancy/forms/filtersets.py:101 @@ -1188,8 +1188,8 @@ msgstr "" #: netbox/circuits/forms/filtersets.py:296 netbox/dcim/forms/bulk_edit.py:1596 #: netbox/extras/forms/model_forms.py:794 netbox/extras/ui/panels.py:446 #: netbox/ipam/forms/filtersets.py:154 netbox/ipam/forms/filtersets.py:663 -#: netbox/ipam/forms/model_forms.py:353 netbox/ipam/ui/panels.py:122 -#: netbox/templates/ipam/vlan_edit.html:42 +#: netbox/ipam/forms/model_forms.py:354 netbox/ipam/ui/panels.py:122 +#: netbox/templates/ipam/vlan_edit.html:46 #: netbox/tenancy/forms/filtersets.py:116 netbox/users/forms/model_forms.py:388 #: netbox/users/forms/model_forms.py:430 msgid "Assignment" @@ -1203,7 +1203,7 @@ msgstr "" #: netbox/dcim/tables/racks.py:232 netbox/dcim/tables/sites.py:69 #: netbox/extras/forms/filtersets.py:612 netbox/ipam/filtersets.py:1049 #: netbox/ipam/forms/bulk_edit.py:428 netbox/ipam/forms/bulk_import.py:522 -#: netbox/ipam/forms/model_forms.py:591 netbox/ipam/tables/fhrp.py:64 +#: netbox/ipam/forms/model_forms.py:592 netbox/ipam/tables/fhrp.py:64 #: netbox/ipam/tables/vlans.py:98 #: netbox/templates/dcim/panels/interface_wireless_lans.html:8 #: netbox/templates/ipam/inc/panels/fhrp_groups.html:23 @@ -1717,7 +1717,7 @@ msgstr "" #: netbox/dcim/tables/devices.py:1055 netbox/dcim/tables/devices.py:1181 #: netbox/dcim/tables/modules.py:84 netbox/extras/forms/filtersets.py:410 #: netbox/ipam/forms/bulk_import.py:316 netbox/ipam/forms/filtersets.py:670 -#: netbox/ipam/forms/model_forms.py:349 netbox/ipam/tables/vlans.py:160 +#: netbox/ipam/forms/model_forms.py:350 netbox/ipam/tables/vlans.py:160 #: netbox/templates/dcim/device_edit.html:12 #: netbox/templates/dcim/panels/interface_connection.html:60 #: netbox/templates/dcim/panels/virtual_chassis_members.html:8 @@ -2211,41 +2211,41 @@ msgstr "" msgid "Must upload a file or select a data file to sync" msgstr "" -#: netbox/core/forms/model_forms.py:156 +#: netbox/core/forms/model_forms.py:147 #: netbox/templates/dcim/rack_elevation_list.html:6 msgid "Rack Elevations" msgstr "" -#: netbox/core/forms/model_forms.py:160 netbox/dcim/choices.py:1947 +#: netbox/core/forms/model_forms.py:151 netbox/dcim/choices.py:1947 #: netbox/dcim/forms/bulk_edit.py:990 netbox/dcim/forms/bulk_edit.py:1396 #: netbox/dcim/forms/bulk_edit.py:1417 netbox/dcim/tables/racks.py:172 #: netbox/netbox/navigation/menu.py:324 netbox/netbox/navigation/menu.py:328 msgid "Power" msgstr "" -#: netbox/core/forms/model_forms.py:162 netbox/netbox/navigation/menu.py:171 +#: netbox/core/forms/model_forms.py:153 netbox/netbox/navigation/menu.py:171 #: netbox/templates/core/inc/config_data.html:37 msgid "IPAM" msgstr "" -#: netbox/core/forms/model_forms.py:163 netbox/netbox/navigation/menu.py:249 +#: netbox/core/forms/model_forms.py:154 netbox/netbox/navigation/menu.py:249 #: netbox/templates/core/inc/config_data.html:50 #: netbox/vpn/forms/bulk_edit.py:65 netbox/vpn/forms/filtersets.py:51 #: netbox/vpn/forms/model_forms.py:59 netbox/vpn/forms/model_forms.py:144 msgid "Security" msgstr "" -#: netbox/core/forms/model_forms.py:164 +#: netbox/core/forms/model_forms.py:155 #: netbox/templates/core/inc/config_data.html:59 msgid "Banners" msgstr "" -#: netbox/core/forms/model_forms.py:165 +#: netbox/core/forms/model_forms.py:156 #: netbox/templates/core/inc/config_data.html:80 msgid "Pagination" msgstr "" -#: netbox/core/forms/model_forms.py:166 netbox/extras/forms/bulk_edit.py:102 +#: netbox/core/forms/model_forms.py:157 netbox/extras/forms/bulk_edit.py:102 #: netbox/extras/forms/filtersets.py:51 netbox/extras/forms/model_forms.py:132 #: netbox/extras/forms/model_forms.py:145 #: netbox/extras/forms/model_forms.py:156 @@ -2253,37 +2253,37 @@ msgstr "" msgid "Validation" msgstr "" -#: netbox/core/forms/model_forms.py:167 +#: netbox/core/forms/model_forms.py:158 #: netbox/templates/account/preferences.html:6 msgid "User Preferences" msgstr "" -#: netbox/core/forms/model_forms.py:168 netbox/netbox/navigation/menu.py:410 +#: netbox/core/forms/model_forms.py:159 netbox/netbox/navigation/menu.py:410 #: netbox/templates/core/objectchange.html:7 #: netbox/templates/core/objectchange_list.html:4 msgid "Change Log" msgstr "" -#: netbox/core/forms/model_forms.py:171 netbox/dcim/forms/filtersets.py:874 +#: netbox/core/forms/model_forms.py:162 netbox/dcim/forms/filtersets.py:874 #: netbox/templates/core/inc/config_data.html:140 #: netbox/users/forms/model_forms.py:78 msgid "Miscellaneous" msgstr "" -#: netbox/core/forms/model_forms.py:173 +#: netbox/core/forms/model_forms.py:164 msgid "Config Revision" msgstr "" -#: netbox/core/forms/model_forms.py:212 +#: netbox/core/forms/model_forms.py:203 msgid "This parameter has been defined statically and cannot be modified." msgstr "" -#: netbox/core/forms/model_forms.py:220 +#: netbox/core/forms/model_forms.py:211 #, python-brace-format msgid "Current value: {value}" msgstr "" -#: netbox/core/forms/model_forms.py:222 +#: netbox/core/forms/model_forms.py:213 msgid " (default)" msgstr "" @@ -3091,7 +3091,7 @@ msgstr "" #: netbox/dcim/tables/devices.py:722 netbox/dcim/tables/devices.py:757 #: netbox/dcim/tables/devices.py:992 netbox/dcim/tables/devices.py:1082 #: netbox/dcim/tables/devices.py:1235 netbox/ipam/forms/bulk_import.py:608 -#: netbox/ipam/forms/model_forms.py:788 netbox/ipam/tables/fhrp.py:56 +#: netbox/ipam/forms/model_forms.py:809 netbox/ipam/tables/fhrp.py:56 #: netbox/ipam/tables/ip.py:334 netbox/ipam/tables/services.py:42 #: netbox/netbox/tables/tables.py:348 netbox/netbox/ui/panels.py:230 #: netbox/tenancy/forms/bulk_edit.py:33 netbox/tenancy/forms/bulk_edit.py:62 @@ -3335,7 +3335,7 @@ msgstr "" msgid "Tagged (All)" msgstr "" -#: netbox/dcim/choices.py:1554 netbox/templates/ipam/vlan_edit.html:26 +#: netbox/dcim/choices.py:1554 netbox/templates/ipam/vlan_edit.html:30 msgid "Q-in-Q (802.1ad)" msgstr "" @@ -4024,10 +4024,10 @@ msgstr "" #: netbox/ipam/forms/bulk_import.py:170 netbox/ipam/forms/bulk_import.py:255 #: netbox/ipam/forms/bulk_import.py:291 netbox/ipam/forms/filtersets.py:70 #: netbox/ipam/forms/filtersets.py:199 netbox/ipam/forms/filtersets.py:353 -#: netbox/ipam/forms/model_forms.py:69 netbox/ipam/forms/model_forms.py:210 -#: netbox/ipam/forms/model_forms.py:274 netbox/ipam/forms/model_forms.py:327 -#: netbox/ipam/forms/model_forms.py:490 netbox/ipam/forms/model_forms.py:510 -#: netbox/ipam/forms/model_forms.py:524 netbox/ipam/models/ip.py:232 +#: netbox/ipam/forms/model_forms.py:70 netbox/ipam/forms/model_forms.py:211 +#: netbox/ipam/forms/model_forms.py:275 netbox/ipam/forms/model_forms.py:328 +#: netbox/ipam/forms/model_forms.py:491 netbox/ipam/forms/model_forms.py:511 +#: netbox/ipam/forms/model_forms.py:525 netbox/ipam/models/ip.py:232 #: netbox/ipam/models/ip.py:670 netbox/ipam/models/ip.py:961 #: netbox/ipam/models/vrfs.py:64 netbox/ipam/tables/ip.py:192 #: netbox/ipam/tables/ip.py:263 netbox/ipam/tables/ip.py:316 @@ -4073,7 +4073,7 @@ msgstr "" #: netbox/dcim/filtersets.py:2189 netbox/dcim/forms/filtersets.py:1726 #: netbox/dcim/forms/model_forms.py:1636 #: netbox/dcim/models/device_components.py:758 -#: netbox/ipam/forms/filtersets.py:552 netbox/ipam/forms/model_forms.py:733 +#: netbox/ipam/forms/filtersets.py:552 netbox/ipam/forms/model_forms.py:754 #: netbox/virtualization/forms/bulk_edit.py:277 #: netbox/virtualization/forms/filtersets.py:313 #: netbox/virtualization/forms/model_forms.py:446 @@ -5602,15 +5602,15 @@ msgid "A virtual chassis member already exists in position {vc_position}." msgstr "" #: netbox/dcim/forms/mixins.py:32 netbox/dcim/forms/mixins.py:93 -#: netbox/ipam/forms/bulk_edit.py:365 netbox/ipam/forms/model_forms.py:630 +#: netbox/ipam/forms/bulk_edit.py:365 netbox/ipam/forms/model_forms.py:631 msgid "Scope type" msgstr "" #: netbox/dcim/forms/mixins.py:35 netbox/dcim/forms/mixins.py:96 #: netbox/ipam/forms/bulk_edit.py:233 netbox/ipam/forms/bulk_edit.py:368 #: netbox/ipam/forms/bulk_edit.py:387 netbox/ipam/forms/filtersets.py:200 -#: netbox/ipam/forms/model_forms.py:232 netbox/ipam/forms/model_forms.py:264 -#: netbox/ipam/forms/model_forms.py:633 netbox/ipam/forms/model_forms.py:643 +#: netbox/ipam/forms/model_forms.py:233 netbox/ipam/forms/model_forms.py:265 +#: netbox/ipam/forms/model_forms.py:634 netbox/ipam/forms/model_forms.py:644 #: netbox/ipam/tables/ip.py:198 netbox/ipam/tables/vlans.py:40 #: netbox/virtualization/forms/bulk_edit.py:76 #: netbox/virtualization/forms/filtersets.py:57 @@ -5886,7 +5886,7 @@ msgid "VM Interface" msgstr "" #: netbox/dcim/forms/model_forms.py:2007 netbox/ipam/forms/filtersets.py:675 -#: netbox/ipam/forms/model_forms.py:350 netbox/ipam/tables/vlans.py:190 +#: netbox/ipam/forms/model_forms.py:351 netbox/ipam/tables/vlans.py:190 #: netbox/virtualization/forms/filtersets.py:264 #: netbox/virtualization/forms/filtersets.py:322 #: netbox/virtualization/forms/model_forms.py:264 @@ -6438,7 +6438,7 @@ msgstr "" #: netbox/dcim/models/device_components.py:751 #: netbox/dcim/tables/devices.py:636 netbox/dcim/ui/panels.py:497 #: netbox/ipam/forms/bulk_edit.py:456 netbox/ipam/forms/bulk_import.py:554 -#: netbox/ipam/forms/filtersets.py:629 netbox/ipam/forms/model_forms.py:714 +#: netbox/ipam/forms/filtersets.py:629 netbox/ipam/forms/model_forms.py:715 #: netbox/ipam/tables/vlans.py:113 netbox/ipam/ui/panels.py:206 #: netbox/virtualization/ui/panels.py:96 msgid "Q-in-Q SVLAN" @@ -7631,8 +7631,8 @@ msgid "U Height" msgstr "" #: netbox/dcim/tables/devices.py:196 netbox/dcim/tables/devices.py:1191 -#: netbox/ipam/forms/bulk_import.py:627 netbox/ipam/forms/model_forms.py:333 -#: netbox/ipam/forms/model_forms.py:345 netbox/ipam/forms/model_forms.py:494 +#: netbox/ipam/forms/bulk_import.py:627 netbox/ipam/forms/model_forms.py:334 +#: netbox/ipam/forms/model_forms.py:346 netbox/ipam/forms/model_forms.py:495 #: netbox/ipam/tables/ip.py:312 netbox/ipam/tables/ip.py:376 #: netbox/ipam/tables/ip.py:391 netbox/ipam/tables/ip.py:414 #: netbox/virtualization/tables/virtualmachines.py:102 @@ -7758,7 +7758,7 @@ msgid "Allocated draw (W)" msgstr "" #: netbox/dcim/tables/devices.py:597 netbox/dcim/views.py:3402 -#: netbox/ipam/forms/model_forms.py:805 netbox/ipam/tables/fhrp.py:28 +#: netbox/ipam/forms/model_forms.py:826 netbox/ipam/tables/fhrp.py:28 #: netbox/ipam/ui/panels.py:252 netbox/ipam/views.py:916 #: netbox/ipam/views.py:1041 netbox/netbox/navigation/menu.py:175 #: netbox/netbox/navigation/menu.py:177 netbox/vpn/tables/tunnels.py:98 @@ -8031,7 +8031,7 @@ msgstr "" #: netbox/dcim/tables/sites.py:22 netbox/dcim/tables/sites.py:41 #: netbox/extras/forms/filtersets.py:446 netbox/extras/forms/model_forms.py:729 -#: netbox/ipam/forms/bulk_edit.py:115 netbox/ipam/forms/model_forms.py:164 +#: netbox/ipam/forms/bulk_edit.py:115 netbox/ipam/forms/model_forms.py:165 #: netbox/ipam/tables/asn.py:80 netbox/netbox/navigation/menu.py:18 #: netbox/netbox/navigation/menu.py:22 msgid "Sites" @@ -9615,139 +9615,139 @@ msgstr "" msgid "Filter must be defined as a dictionary mapping attributes to values." msgstr "" -#: netbox/extras/models/customfields.py:542 +#: netbox/extras/models/customfields.py:544 msgid "True" msgstr "" -#: netbox/extras/models/customfields.py:543 +#: netbox/extras/models/customfields.py:545 msgid "False" msgstr "" -#: netbox/extras/models/customfields.py:596 -#: netbox/extras/models/customfields.py:649 +#: netbox/extras/models/customfields.py:598 +#: netbox/extras/models/customfields.py:651 #, python-brace-format msgid "Values must match this regex: {regex}" msgstr "" -#: netbox/extras/models/customfields.py:751 -#: netbox/extras/models/customfields.py:758 +#: netbox/extras/models/customfields.py:753 +#: netbox/extras/models/customfields.py:760 msgid "Value must be a string." msgstr "" -#: netbox/extras/models/customfields.py:753 -#: netbox/extras/models/customfields.py:760 +#: netbox/extras/models/customfields.py:755 +#: netbox/extras/models/customfields.py:762 #, python-brace-format msgid "Value must match regex '{regex}'" msgstr "" -#: netbox/extras/models/customfields.py:765 +#: netbox/extras/models/customfields.py:767 msgid "Value must be an integer." msgstr "" -#: netbox/extras/models/customfields.py:768 -#: netbox/extras/models/customfields.py:783 +#: netbox/extras/models/customfields.py:770 +#: netbox/extras/models/customfields.py:785 #, python-brace-format msgid "Value must be at least {minimum}" msgstr "" -#: netbox/extras/models/customfields.py:772 -#: netbox/extras/models/customfields.py:787 +#: netbox/extras/models/customfields.py:774 +#: netbox/extras/models/customfields.py:789 #, python-brace-format msgid "Value must not exceed {maximum}" msgstr "" -#: netbox/extras/models/customfields.py:780 +#: netbox/extras/models/customfields.py:782 msgid "Value must be a decimal." msgstr "" -#: netbox/extras/models/customfields.py:792 +#: netbox/extras/models/customfields.py:794 msgid "Value must be true or false." msgstr "" -#: netbox/extras/models/customfields.py:800 +#: netbox/extras/models/customfields.py:802 msgid "Date values must be in ISO 8601 format (YYYY-MM-DD)." msgstr "" -#: netbox/extras/models/customfields.py:809 +#: netbox/extras/models/customfields.py:811 msgid "Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS)." msgstr "" -#: netbox/extras/models/customfields.py:816 +#: netbox/extras/models/customfields.py:818 #, python-brace-format msgid "Invalid choice ({value}) for choice set {choiceset}." msgstr "" -#: netbox/extras/models/customfields.py:826 +#: netbox/extras/models/customfields.py:828 #, python-brace-format msgid "Invalid choice(s) ({value}) for choice set {choiceset}." msgstr "" -#: netbox/extras/models/customfields.py:835 +#: netbox/extras/models/customfields.py:837 #, python-brace-format msgid "Value must be an object ID, not {type}" msgstr "" -#: netbox/extras/models/customfields.py:841 +#: netbox/extras/models/customfields.py:843 #, python-brace-format msgid "Value must be a list of object IDs, not {type}" msgstr "" -#: netbox/extras/models/customfields.py:845 +#: netbox/extras/models/customfields.py:847 #, python-brace-format msgid "Found invalid object ID: {id}" msgstr "" -#: netbox/extras/models/customfields.py:854 +#: netbox/extras/models/customfields.py:856 #, python-brace-format msgid "Value does not conform to the assigned schema: {error}" msgstr "" -#: netbox/extras/models/customfields.py:858 +#: netbox/extras/models/customfields.py:860 msgid "Required field cannot be empty." msgstr "" -#: netbox/extras/models/customfields.py:878 +#: netbox/extras/models/customfields.py:880 msgid "Base set of predefined choices (optional)" msgstr "" -#: netbox/extras/models/customfields.py:890 +#: netbox/extras/models/customfields.py:892 msgid "Choices are automatically ordered alphabetically" msgstr "" -#: netbox/extras/models/customfields.py:897 +#: netbox/extras/models/customfields.py:899 msgid "custom field choice set" msgstr "" -#: netbox/extras/models/customfields.py:898 +#: netbox/extras/models/customfields.py:900 msgid "custom field choice sets" msgstr "" -#: netbox/extras/models/customfields.py:958 +#: netbox/extras/models/customfields.py:960 msgid "Must define base or extra choices." msgstr "" -#: netbox/extras/models/customfields.py:964 +#: netbox/extras/models/customfields.py:966 msgid "Color mappings must be defined as a JSON object." msgstr "" -#: netbox/extras/models/customfields.py:976 +#: netbox/extras/models/customfields.py:978 #, python-brace-format msgid "Duplicate value '{value}' found in extra choices." msgstr "" -#: netbox/extras/models/customfields.py:993 +#: netbox/extras/models/customfields.py:995 #, python-brace-format msgid "" "Color mappings must reference an existing choice value. Invalid value(s): " "{values}." msgstr "" -#: netbox/extras/models/customfields.py:1000 +#: netbox/extras/models/customfields.py:1002 #, python-brace-format msgid "Invalid color value(s): {colors}. Use a supported named color." msgstr "" -#: netbox/extras/models/customfields.py:1023 +#: netbox/extras/models/customfields.py:1025 #, python-brace-format msgid "" "Cannot remove choice {choice} as there are {model} objects which reference " @@ -10378,7 +10378,7 @@ msgstr "" #: netbox/templates/generic/bulk_edit.html:89 #: netbox/templates/htmx/form.html:19 netbox/templates/inc/filter_list.html:30 #: netbox/templates/inc/panels/custom_fields.html:7 -#: netbox/templates/ipam/vlan_edit.html:79 +#: netbox/templates/ipam/vlan_edit.html:83 msgid "Custom Fields" msgstr "" @@ -10676,7 +10676,7 @@ msgid "Exporting L2VPN (identifier)" msgstr "" #: netbox/ipam/filtersets.py:173 netbox/ipam/filtersets.py:336 -#: netbox/ipam/forms/model_forms.py:230 netbox/ipam/forms/model_forms.py:262 +#: netbox/ipam/forms/model_forms.py:231 netbox/ipam/forms/model_forms.py:263 #: netbox/ipam/tables/ip.py:162 #: netbox/templates/ipam/inc/prefix_edit_header.html:7 msgid "Prefix" @@ -10727,7 +10727,7 @@ msgid "VLAN number (1-4094)" msgstr "" #: netbox/ipam/filtersets.py:510 netbox/ipam/filtersets.py:514 -#: netbox/ipam/filtersets.py:612 netbox/ipam/forms/model_forms.py:528 +#: netbox/ipam/filtersets.py:612 netbox/ipam/forms/model_forms.py:529 #: netbox/tenancy/forms/bulk_edit.py:108 msgid "Address" msgstr "" @@ -10829,11 +10829,21 @@ msgstr "" msgid "CIDR mask (e.g. /24) is required." msgstr "" -#: netbox/ipam/forms/bulk_create.py:16 +#: netbox/ipam/forms/bulk_create.py:18 #: netbox/templates/generic/bulk_add.html:26 msgid "Pattern" msgstr "" +#: netbox/ipam/forms/bulk_create.py:28 netbox/ipam/forms/model_forms.py:625 +#: netbox/ipam/ui/panels.py:162 +msgid "VLAN IDs" +msgstr "" + +#: netbox/ipam/forms/bulk_create.py:30 +msgid "" +"Enter VLAN IDs and ranges separated by commas. Example: 100,200-210,3100-3299" +msgstr "" + #: netbox/ipam/forms/bulk_edit.py:56 msgid "Enforce unique space" msgstr "" @@ -10847,8 +10857,8 @@ msgstr "" #: netbox/ipam/forms/bulk_import.py:116 netbox/ipam/forms/bulk_import.py:136 #: netbox/ipam/forms/filtersets.py:91 netbox/ipam/forms/filtersets.py:121 #: netbox/ipam/forms/filtersets.py:137 netbox/ipam/forms/filtersets.py:161 -#: netbox/ipam/forms/model_forms.py:100 netbox/ipam/forms/model_forms.py:113 -#: netbox/ipam/forms/model_forms.py:135 netbox/ipam/forms/model_forms.py:153 +#: netbox/ipam/forms/model_forms.py:101 netbox/ipam/forms/model_forms.py:114 +#: netbox/ipam/forms/model_forms.py:136 netbox/ipam/forms/model_forms.py:154 #: netbox/ipam/models/asns.py:32 netbox/ipam/models/asns.py:132 #: netbox/ipam/models/ip.py:72 netbox/ipam/models/ip.py:88 #: netbox/ipam/tables/asn.py:20 netbox/ipam/tables/asn.py:55 @@ -10862,16 +10872,18 @@ msgid "Date added" msgstr "" #: netbox/ipam/forms/bulk_edit.py:182 netbox/ipam/forms/filtersets.py:286 -#: netbox/ipam/forms/model_forms.py:641 netbox/ipam/forms/model_forms.py:690 -#: netbox/ipam/tables/ip.py:205 netbox/templates/ipam/vlan_edit.html:49 +#: netbox/ipam/forms/model_forms.py:642 netbox/ipam/forms/model_forms.py:691 +#: netbox/ipam/tables/ip.py:205 netbox/templates/ipam/vlan_edit.html:53 msgid "VLAN Group" msgstr "" #: netbox/ipam/forms/bulk_edit.py:187 netbox/ipam/forms/bulk_import.py:198 -#: netbox/ipam/forms/filtersets.py:291 netbox/ipam/forms/model_forms.py:219 -#: netbox/ipam/models/vlans.py:307 netbox/ipam/tables/ip.py:210 -#: netbox/ipam/ui/panels.py:148 netbox/templates/ipam/vlan/base.html:6 -#: netbox/templates/ipam/vlan_edit.html:14 netbox/vpn/forms/bulk_import.py:309 +#: netbox/ipam/forms/filtersets.py:291 netbox/ipam/forms/model_forms.py:220 +#: netbox/ipam/forms/model_forms.py:739 netbox/ipam/models/vlans.py:307 +#: netbox/ipam/tables/ip.py:210 netbox/ipam/ui/panels.py:148 +#: netbox/templates/ipam/inc/vlan_edit_header.html:9 +#: netbox/templates/ipam/vlan/base.html:6 +#: netbox/templates/ipam/vlan_edit.html:18 netbox/vpn/forms/bulk_import.py:309 #: netbox/vpn/forms/filtersets.py:306 netbox/vpn/forms/model_forms.py:431 #: netbox/vpn/forms/model_forms.py:450 netbox/wireless/forms/bulk_edit.py:52 #: netbox/wireless/forms/bulk_import.py:49 @@ -10896,7 +10908,7 @@ msgid "Treat as fully utilized" msgstr "" #: netbox/ipam/forms/bulk_edit.py:234 netbox/ipam/forms/filtersets.py:198 -#: netbox/ipam/forms/model_forms.py:233 netbox/ipam/forms/model_forms.py:265 +#: netbox/ipam/forms/model_forms.py:234 netbox/ipam/forms/model_forms.py:266 msgid "VLAN Assignment" msgstr "" @@ -10938,7 +10950,7 @@ msgid "Authentication key" msgstr "" #: netbox/ipam/forms/bulk_edit.py:355 netbox/ipam/forms/filtersets.py:435 -#: netbox/ipam/forms/model_forms.py:538 netbox/ipam/ui/panels.py:189 +#: netbox/ipam/forms/model_forms.py:539 netbox/ipam/ui/panels.py:189 #: netbox/netbox/navigation/menu.py:421 netbox/wireless/forms/bulk_edit.py:83 #: netbox/wireless/forms/bulk_edit.py:135 #: netbox/wireless/forms/filtersets.py:43 @@ -10967,13 +10979,13 @@ msgid "Site & Group" msgstr "" #: netbox/ipam/forms/bulk_edit.py:485 netbox/ipam/forms/bulk_import.py:578 -#: netbox/ipam/forms/model_forms.py:745 netbox/ipam/tables/vlans.py:234 +#: netbox/ipam/forms/model_forms.py:766 netbox/ipam/tables/vlans.py:234 #: netbox/vpn/forms/model_forms.py:319 netbox/vpn/forms/model_forms.py:356 msgid "Policy" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:506 netbox/ipam/forms/model_forms.py:763 -#: netbox/ipam/forms/model_forms.py:795 netbox/ipam/tables/services.py:20 +#: netbox/ipam/forms/bulk_edit.py:506 netbox/ipam/forms/model_forms.py:784 +#: netbox/ipam/forms/model_forms.py:816 netbox/ipam/tables/services.py:20 #: netbox/ipam/tables/services.py:47 netbox/ipam/ui/panels.py:240 #: netbox/ipam/ui/panels.py:248 msgid "Ports" @@ -11011,8 +11023,8 @@ msgid "Scope ID" msgstr "" #: netbox/ipam/forms/bulk_import.py:337 netbox/ipam/forms/filtersets.py:680 -#: netbox/ipam/forms/model_forms.py:322 netbox/ipam/forms/model_forms.py:351 -#: netbox/ipam/forms/model_forms.py:537 +#: netbox/ipam/forms/model_forms.py:323 netbox/ipam/forms/model_forms.py:352 +#: netbox/ipam/forms/model_forms.py:538 msgid "FHRP Group" msgstr "" @@ -11094,17 +11106,17 @@ msgstr "" msgid "{ip} is not assigned to this parent." msgstr "" -#: netbox/ipam/forms/filtersets.py:49 netbox/ipam/forms/model_forms.py:70 +#: netbox/ipam/forms/filtersets.py:49 netbox/ipam/forms/model_forms.py:71 #: netbox/netbox/navigation/menu.py:206 netbox/vpn/forms/model_forms.py:408 msgid "Route Targets" msgstr "" -#: netbox/ipam/forms/filtersets.py:56 netbox/ipam/forms/model_forms.py:58 +#: netbox/ipam/forms/filtersets.py:56 netbox/ipam/forms/model_forms.py:59 #: netbox/vpn/forms/filtersets.py:246 netbox/vpn/forms/model_forms.py:396 msgid "Import targets" msgstr "" -#: netbox/ipam/forms/filtersets.py:61 netbox/ipam/forms/model_forms.py:63 +#: netbox/ipam/forms/filtersets.py:61 netbox/ipam/forms/model_forms.py:64 #: netbox/vpn/forms/filtersets.py:251 netbox/vpn/forms/model_forms.py:401 msgid "Export targets" msgstr "" @@ -11185,7 +11197,7 @@ msgstr "" msgid "Remote VLAN ID" msgstr "" -#: netbox/ipam/forms/filtersets.py:572 +#: netbox/ipam/forms/filtersets.py:572 netbox/ipam/forms/model_forms.py:740 msgid "Q-in-Q/802.1ad" msgstr "" @@ -11194,120 +11206,121 @@ msgstr "" msgid "VLAN ID" msgstr "" -#: netbox/ipam/forms/model_forms.py:87 +#: netbox/ipam/forms/model_forms.py:88 msgid "Route Target" msgstr "" -#: netbox/ipam/forms/model_forms.py:118 netbox/ipam/tables/ip.py:65 +#: netbox/ipam/forms/model_forms.py:119 netbox/ipam/tables/ip.py:65 #: netbox/ipam/ui/panels.py:145 msgid "Aggregate" msgstr "" -#: netbox/ipam/forms/model_forms.py:139 +#: netbox/ipam/forms/model_forms.py:140 msgid "ASN Range" msgstr "" -#: netbox/ipam/forms/model_forms.py:286 +#: netbox/ipam/forms/model_forms.py:287 msgid "IP Range" msgstr "" -#: netbox/ipam/forms/model_forms.py:337 +#: netbox/ipam/forms/model_forms.py:338 msgid "Make this the primary IP for the device/VM" msgstr "" -#: netbox/ipam/forms/model_forms.py:341 +#: netbox/ipam/forms/model_forms.py:342 msgid "Make this the out-of-band IP for the device" msgstr "" -#: netbox/ipam/forms/model_forms.py:355 +#: netbox/ipam/forms/model_forms.py:356 msgid "NAT IP (Inside)" msgstr "" -#: netbox/ipam/forms/model_forms.py:417 +#: netbox/ipam/forms/model_forms.py:418 msgid "An IP address can only be assigned to a single object." msgstr "" -#: netbox/ipam/forms/model_forms.py:424 +#: netbox/ipam/forms/model_forms.py:425 msgid "Cannot reassign primary IP address for the parent device/VM" msgstr "" -#: netbox/ipam/forms/model_forms.py:428 +#: netbox/ipam/forms/model_forms.py:429 msgid "Cannot reassign out-of-Band IP address for the parent device" msgstr "" -#: netbox/ipam/forms/model_forms.py:438 +#: netbox/ipam/forms/model_forms.py:439 msgid "" "Only IP addresses assigned to an interface can be designated as primary IPs." msgstr "" -#: netbox/ipam/forms/model_forms.py:446 +#: netbox/ipam/forms/model_forms.py:447 msgid "" "Only IP addresses assigned to a device interface can be designated as the " "out-of-band IP for a device." msgstr "" -#: netbox/ipam/forms/model_forms.py:539 +#: netbox/ipam/forms/model_forms.py:540 msgid "Virtual IP Address" msgstr "" -#: netbox/ipam/forms/model_forms.py:616 +#: netbox/ipam/forms/model_forms.py:617 msgid "Assignment already exists" msgstr "" -#: netbox/ipam/forms/model_forms.py:624 netbox/ipam/ui/panels.py:162 -msgid "VLAN IDs" -msgstr "" - -#: netbox/ipam/forms/model_forms.py:642 +#: netbox/ipam/forms/model_forms.py:643 msgid "Child VLANs" msgstr "" -#: netbox/ipam/forms/model_forms.py:701 +#: netbox/ipam/forms/model_forms.py:702 msgid "" "The direct assignment of VLANs to a site is deprecated and will be removed " "in a future release. Users are encouraged to utilize VLAN groups for this " "purpose." msgstr "" -#: netbox/ipam/forms/model_forms.py:751 +#: netbox/ipam/forms/model_forms.py:747 +#, python-brace-format +msgid "Use {vid} as a placeholder for the VLAN ID. Example: VLAN-{vid}." +msgstr "" + +#: netbox/ipam/forms/model_forms.py:772 msgid "VLAN Translation Rule" msgstr "" -#: netbox/ipam/forms/model_forms.py:768 netbox/ipam/forms/model_forms.py:800 +#: netbox/ipam/forms/model_forms.py:789 netbox/ipam/forms/model_forms.py:821 msgid "" "Comma-separated list of one or more port numbers. A range may be specified " "using a hyphen." msgstr "" -#: netbox/ipam/forms/model_forms.py:772 +#: netbox/ipam/forms/model_forms.py:793 msgid "Application Service Template" msgstr "" -#: netbox/ipam/forms/model_forms.py:785 +#: netbox/ipam/forms/model_forms.py:806 msgid "Parent type" msgstr "" -#: netbox/ipam/forms/model_forms.py:811 +#: netbox/ipam/forms/model_forms.py:832 msgid "Port(s)" msgstr "" -#: netbox/ipam/forms/model_forms.py:812 netbox/ipam/forms/model_forms.py:878 +#: netbox/ipam/forms/model_forms.py:833 netbox/ipam/forms/model_forms.py:899 msgid "Application Service" msgstr "" -#: netbox/ipam/forms/model_forms.py:866 +#: netbox/ipam/forms/model_forms.py:887 msgid "Application Service template" msgstr "" -#: netbox/ipam/forms/model_forms.py:875 +#: netbox/ipam/forms/model_forms.py:896 msgid "From Template" msgstr "" -#: netbox/ipam/forms/model_forms.py:876 +#: netbox/ipam/forms/model_forms.py:897 msgid "Custom" msgstr "" -#: netbox/ipam/forms/model_forms.py:908 +#: netbox/ipam/forms/model_forms.py:929 msgid "" "Must specify name, protocol, and port(s) if not using an application service " "template." @@ -12515,6 +12528,10 @@ msgstr "" msgid "Lookup" msgstr "" +#: netbox/netbox/middleware.py:301 +msgid "Single sign-on failed. Please try again or contact your administrator." +msgstr "" + #: netbox/netbox/models/deletion.py:76 #, python-brace-format msgid "" @@ -13164,67 +13181,67 @@ msgstr "" msgid "Cannot delete stores from registry" msgstr "" -#: netbox/netbox/settings.py:873 +#: netbox/netbox/settings.py:881 msgid "Czech" msgstr "" -#: netbox/netbox/settings.py:874 +#: netbox/netbox/settings.py:882 msgid "Danish" msgstr "" -#: netbox/netbox/settings.py:875 +#: netbox/netbox/settings.py:883 msgid "German" msgstr "" -#: netbox/netbox/settings.py:876 +#: netbox/netbox/settings.py:884 msgid "English" msgstr "" -#: netbox/netbox/settings.py:877 +#: netbox/netbox/settings.py:885 msgid "Spanish" msgstr "" -#: netbox/netbox/settings.py:878 +#: netbox/netbox/settings.py:886 msgid "French" msgstr "" -#: netbox/netbox/settings.py:879 +#: netbox/netbox/settings.py:887 msgid "Italian" msgstr "" -#: netbox/netbox/settings.py:880 +#: netbox/netbox/settings.py:888 msgid "Japanese" msgstr "" -#: netbox/netbox/settings.py:881 +#: netbox/netbox/settings.py:889 msgid "Latvian" msgstr "" -#: netbox/netbox/settings.py:882 +#: netbox/netbox/settings.py:890 msgid "Dutch" msgstr "" -#: netbox/netbox/settings.py:883 +#: netbox/netbox/settings.py:891 msgid "Polish" msgstr "" -#: netbox/netbox/settings.py:884 +#: netbox/netbox/settings.py:892 msgid "Portuguese" msgstr "" -#: netbox/netbox/settings.py:885 +#: netbox/netbox/settings.py:893 msgid "Russian" msgstr "" -#: netbox/netbox/settings.py:886 +#: netbox/netbox/settings.py:894 msgid "Turkish" msgstr "" -#: netbox/netbox/settings.py:887 +#: netbox/netbox/settings.py:895 msgid "Ukrainian" msgstr "" -#: netbox/netbox/settings.py:888 +#: netbox/netbox/settings.py:896 msgid "Chinese" msgstr "" @@ -13293,90 +13310,99 @@ msgid "" "{error}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:424 +#: netbox/netbox/views/generic/bulk_views.py:321 +msgid "General" +msgstr "" + +#: netbox/netbox/views/generic/bulk_views.py:331 +#, python-brace-format +msgid "{value}: {field}: {error}" +msgstr "" + +#: netbox/netbox/views/generic/bulk_views.py:506 msgid "Must be a list." msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:434 +#: netbox/netbox/views/generic/bulk_views.py:516 msgid "Must be a dictionary." msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:491 +#: netbox/netbox/views/generic/bulk_views.py:573 #, python-brace-format msgid "Object with ID {id} does not exist" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:554 +#: netbox/netbox/views/generic/bulk_views.py:636 #, python-brace-format msgid "" "Duplicate objects found: {model} with ID(s) {ids} appears multiple times" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:606 +#: netbox/netbox/views/generic/bulk_views.py:688 #, python-brace-format msgid "Bulk import {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:622 +#: netbox/netbox/views/generic/bulk_views.py:704 #, python-brace-format msgid "Imported {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:736 +#: netbox/netbox/views/generic/bulk_views.py:818 #, python-brace-format msgid "Custom field form field name must begin with 'cf_': {name}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:815 +#: netbox/netbox/views/generic/bulk_views.py:897 #, python-brace-format msgid "Bulk edit {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:831 +#: netbox/netbox/views/generic/bulk_views.py:913 #, python-brace-format msgid "Updated {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:864 -#: netbox/netbox/views/generic/bulk_views.py:1143 -#: netbox/netbox/views/generic/bulk_views.py:1191 +#: netbox/netbox/views/generic/bulk_views.py:946 +#: netbox/netbox/views/generic/bulk_views.py:1225 +#: netbox/netbox/views/generic/bulk_views.py:1273 #, python-brace-format msgid "No {object_type} were selected." msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:971 +#: netbox/netbox/views/generic/bulk_views.py:1053 msgid "Select at least one field to rename." msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1001 +#: netbox/netbox/views/generic/bulk_views.py:1083 #, python-brace-format msgid "Renamed {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1072 +#: netbox/netbox/views/generic/bulk_views.py:1154 #, python-brace-format msgid "Bulk delete {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1099 +#: netbox/netbox/views/generic/bulk_views.py:1181 #, python-brace-format msgid "Deleted {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1116 +#: netbox/netbox/views/generic/bulk_views.py:1198 msgid "Deletion failed due to the presence of one or more dependent objects." msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1204 +#: netbox/netbox/views/generic/bulk_views.py:1286 #, python-brace-format msgid "Bulk add {count} {object_type}" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1257 +#: netbox/netbox/views/generic/bulk_views.py:1339 msgid "An integrity error occurred while creating components" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:1269 +#: netbox/netbox/views/generic/bulk_views.py:1351 #, python-brace-format msgid "Added {count} {component} to {parent_count} {parent}." msgstr "" @@ -13638,6 +13664,7 @@ msgstr "" #: netbox/templates/htmx/quick_add.html:24 #: netbox/templates/ipam/inc/ipaddress_edit_header.html:7 #: netbox/templates/ipam/inc/prefix_edit_header.html:7 +#: netbox/templates/ipam/inc/vlan_edit_header.html:11 #: netbox/templates/users/token_edit.html:36 msgid "Create" msgstr "" @@ -14983,6 +15010,7 @@ msgstr "" #: netbox/templates/generic/bulk_add.html:17 #: netbox/templates/ipam/inc/ipaddress_edit_header.html:19 #: netbox/templates/ipam/inc/prefix_edit_header.html:13 +#: netbox/templates/ipam/inc/vlan_edit_header.html:19 msgid "Bulk Create" msgstr "" From 025074c390c379602cdb9f1f704227408996dbf1 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Tue, 16 Jun 2026 11:38:21 +0200 Subject: [PATCH 52/58] Closes #22280: Set 91% test coverage threshold and exclude non-testable paths (#22450) --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 600931e99..87bd6465f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,9 +34,17 @@ sigterm = true [tool.coverage.report] skip_covered = true +fail_under = 91 omit = [ "*/migrations/*", "*/tests/*", + # Non-application code (no testable logic / not part of the app) + "netbox/scripts/*", # SCRIPTS_ROOT: user/generated scripts + "*/netbox/configuration*.py", # settings/config files (template, testing, local) + "*/netbox/wsgi.py", # WSGI entrypoint + "*/generate_secret_key.py", # standalone CLI helper + "*/utilities/debug.py", # debug-toolbar hook, active only when DEBUG=True + "*/extras/management/commands/housekeeping.py", # deprecated; will not be tested ] [tool.pyright] From 0994ce9f0c7e69b866173c17384a64cc32b69127 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 16 Jun 2026 05:51:36 -0400 Subject: [PATCH 53/58] Closes #22457: Use `hmac.compare_digest()` to authenticate API tokens (#22458) --- netbox/users/models/tokens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/users/models/tokens.py b/netbox/users/models/tokens.py index 3c5afa94e..3ec614c01 100644 --- a/netbox/users/models/tokens.py +++ b/netbox/users/models/tokens.py @@ -282,7 +282,7 @@ class Token(models.Model): digest. """ if self.v1: - return token == self.token + return hmac.compare_digest(token, self.plaintext) if self.v2: token = token.removeprefix(TOKEN_PREFIX) try: @@ -291,7 +291,7 @@ class Token(models.Model): # Invalid pepper ID return False digest = hmac.new(pepper.encode('utf-8'), token.encode('utf-8'), hashlib.sha256).hexdigest() - return digest == self.hmac_digest + return hmac.compare_digest(digest, self.hmac_digest) return False def validate_client_ip(self, client_ip): From 16c70c3657728052d0736aed32081d84371cb573 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 16 Jun 2026 07:43:52 -0400 Subject: [PATCH 54/58] Fixes #22448: Ensure all objects are escaped under handle_protectederror() (#22449) --- netbox/utilities/error_handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/utilities/error_handlers.py b/netbox/utilities/error_handlers.py index 397098ded..8c0dce57d 100644 --- a/netbox/utilities/error_handlers.py +++ b/netbox/utilities/error_handlers.py @@ -29,7 +29,7 @@ def handle_protectederror(obj_list, request, e): # Formulate the error message err_message = _("Unable to delete {objects}. {count} dependent objects were found: ").format( - objects=', '.join(str(obj) for obj in obj_list), + objects=', '.join(escape(obj) for obj in obj_list), count=len(protected_objects) if len(protected_objects) <= 50 else _('More than 50') ) From f46090076d800b2bd67e0e835d9db8c429079fc2 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Mon, 15 Jun 2026 18:01:04 +0200 Subject: [PATCH 55/58] refactor(graphql): Update filter lookups for strawberry-django 0.86 Update strawberry-graphql-django to 0.86.1 and remove redundant type parameters from StrFilterLookup, DateFilterLookup, TimeFilterLookup, and DatetimeFilterLookup annotations across model-backed GraphQL filters. Add NetBox-local JSON date, time, and datetime lookup input types to preserve the previous string-backed JSON filter schema without relying on deprecated upstream generic lookup annotations. These local types keep the legacy GraphQL type names and date/time sub-lookup fields intact. Fixes #22353 --- netbox/circuits/graphql/filters.py | 29 +++-- netbox/core/graphql/filter_mixins.py | 5 +- netbox/core/graphql/filters.py | 31 +++-- netbox/core/tests/test_api.py | 46 ++++++- netbox/dcim/graphql/filter_mixins.py | 12 +- netbox/dcim/graphql/filters.py | 82 ++++++------- netbox/extras/graphql/filters.py | 113 +++++++++--------- netbox/ipam/graphql/filters.py | 41 ++++--- netbox/netbox/graphql/filter_lookups.py | 53 ++++++-- netbox/netbox/graphql/filter_mixins.py | 5 +- netbox/netbox/graphql/filters.py | 18 +-- netbox/netbox/tests/test_graphql.py | 27 ++++- netbox/tenancy/graphql/filters.py | 16 +-- netbox/users/graphql/filters.py | 25 ++-- .../virtualization/graphql/filter_mixins.py | 4 +- netbox/virtualization/graphql/filters.py | 6 +- netbox/vpn/graphql/filters.py | 18 +-- netbox/wireless/graphql/filter_mixins.py | 2 +- netbox/wireless/graphql/filters.py | 4 +- requirements.txt | 2 +- 20 files changed, 319 insertions(+), 220 deletions(-) diff --git a/netbox/circuits/graphql/filters.py b/netbox/circuits/graphql/filters.py index 94ec9fec2..238b488ac 100644 --- a/netbox/circuits/graphql/filters.py +++ b/netbox/circuits/graphql/filters.py @@ -1,4 +1,3 @@ -from datetime import date from typing import TYPE_CHECKING, Annotated import strawberry @@ -62,9 +61,9 @@ class CircuitTerminationFilter( upstream_speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - xconnect_id: StrFilterLookup[str] | None = strawberry_django.filter_field() - pp_info: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + xconnect_id: StrFilterLookup | None = strawberry_django.filter_field() + pp_info: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() # Cached relations _provider_network: Annotated['ProviderNetworkFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( @@ -92,7 +91,7 @@ class CircuitFilter( TenancyFilterMixin, PrimaryModelFilter ): - cid: StrFilterLookup[str] | None = strawberry_django.filter_field() + cid: StrFilterLookup | None = strawberry_django.filter_field() provider: Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -108,8 +107,8 @@ class CircuitFilter( status: BaseFilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - install_date: DateFilterLookup[date] | None = strawberry_django.filter_field() - termination_date: DateFilterLookup[date] | None = strawberry_django.filter_field() + install_date: DateFilterLookup | None = strawberry_django.filter_field() + termination_date: DateFilterLookup | None = strawberry_django.filter_field() commit_rate: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -145,8 +144,8 @@ class CircuitGroupAssignmentFilter(CustomFieldsFilterMixin, TagsFilterMixin, Cha @strawberry_django.filter_type(models.Provider, lookups=True) class ProviderFilter(ContactFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() asns: Annotated['ASNFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() circuits: Annotated['CircuitFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( strawberry_django.filter_field() @@ -159,18 +158,18 @@ class ProviderAccountFilter(ContactFilterMixin, PrimaryModelFilter): strawberry_django.filter_field() ) provider_id: ID | None = strawberry_django.filter_field() - account: StrFilterLookup[str] | None = strawberry_django.filter_field() - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + account: StrFilterLookup | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.ProviderNetwork, lookups=True) class ProviderNetworkFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() provider: Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( strawberry_django.filter_field() ) provider_id: ID | None = strawberry_django.filter_field() - service_id: StrFilterLookup[str] | None = strawberry_django.filter_field() + service_id: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.VirtualCircuitType, lookups=True) @@ -180,7 +179,7 @@ class VirtualCircuitTypeFilter(CircuitTypeFilterMixin, OrganizationalModelFilter @strawberry_django.filter_type(models.VirtualCircuit, lookups=True) class VirtualCircuitFilter(TenancyFilterMixin, PrimaryModelFilter): - cid: StrFilterLookup[str] | None = strawberry_django.filter_field() + cid: StrFilterLookup | None = strawberry_django.filter_field() provider_network: Annotated['ProviderNetworkFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -218,4 +217,4 @@ class VirtualCircuitTerminationFilter(CustomFieldsFilterMixin, TagsFilterMixin, strawberry_django.filter_field() ) interface_id: ID | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() diff --git a/netbox/core/graphql/filter_mixins.py b/netbox/core/graphql/filter_mixins.py index c094ecb24..96c4b7c29 100644 --- a/netbox/core/graphql/filter_mixins.py +++ b/netbox/core/graphql/filter_mixins.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from datetime import datetime from typing import TYPE_CHECKING, Annotated import strawberry @@ -20,5 +19,5 @@ class ChangeLoggingMixin: changelog: Annotated['ObjectChangeFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) - created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + created: DatetimeFilterLookup | None = strawberry_django.filter_field() + last_updated: DatetimeFilterLookup | None = strawberry_django.filter_field() diff --git a/netbox/core/graphql/filters.py b/netbox/core/graphql/filters.py index 3424c5737..05f4803e4 100644 --- a/netbox/core/graphql/filters.py +++ b/netbox/core/graphql/filters.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import TYPE_CHECKING, Annotated import strawberry @@ -26,33 +25,33 @@ __all__ = ( @strawberry_django.filter_type(models.DataFile, lookups=True) class DataFileFilter(BaseModelFilter): - created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + created: DatetimeFilterLookup | None = strawberry_django.filter_field() + last_updated: DatetimeFilterLookup | None = strawberry_django.filter_field() source: Annotated['DataSourceFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) source_id: ID | None = strawberry_django.filter_field() - path: StrFilterLookup[str] | None = strawberry_django.filter_field() + path: StrFilterLookup | None = strawberry_django.filter_field() size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - hash: StrFilterLookup[str] | None = strawberry_django.filter_field() + hash: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.DataSource, lookups=True) class DataSourceFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - type: StrFilterLookup[str] | None = strawberry_django.filter_field() - source_url: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + type: StrFilterLookup | None = strawberry_django.filter_field() + source_url: StrFilterLookup | None = strawberry_django.filter_field() status: ( BaseFilterLookup[Annotated['DataSourceStatusEnum', strawberry.lazy('core.graphql.enums')]] | None ) = strawberry_django.filter_field() enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - ignore_rules: StrFilterLookup[str] | None = strawberry_django.filter_field() + ignore_rules: StrFilterLookup | None = strawberry_django.filter_field() parameters: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - last_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + last_synced: DatetimeFilterLookup | None = strawberry_django.filter_field() datafiles: Annotated['DataFileFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -60,10 +59,10 @@ class DataSourceFilter(PrimaryModelFilter): @strawberry_django.filter_type(models.ObjectChange, lookups=True) class ObjectChangeFilter(BaseModelFilter): - time: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + time: DatetimeFilterLookup | None = strawberry_django.filter_field() user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() - user_name: StrFilterLookup[str] | None = strawberry_django.filter_field() - request_id: StrFilterLookup[str] | None = strawberry_django.filter_field() + user_name: StrFilterLookup | None = strawberry_django.filter_field() + request_id: StrFilterLookup | None = strawberry_django.filter_field() action: ( BaseFilterLookup[Annotated['ObjectChangeActionEnum', strawberry.lazy('core.graphql.enums')]] | None ) = strawberry_django.filter_field() @@ -76,7 +75,7 @@ class ObjectChangeFilter(BaseModelFilter): strawberry_django.filter_field() ) related_object_id: ID | None = strawberry_django.filter_field() - object_repr: StrFilterLookup[str] | None = strawberry_django.filter_field() + object_repr: StrFilterLookup | None = strawberry_django.filter_field() prechange_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -87,5 +86,5 @@ class ObjectChangeFilter(BaseModelFilter): @strawberry_django.filter_type(DjangoContentType, lookups=True) class ContentTypeFilter(BaseModelFilter): - app_label: StrFilterLookup[str] | None = strawberry_django.filter_field() - model: StrFilterLookup[str] | None = strawberry_django.filter_field() + app_label: StrFilterLookup | None = strawberry_django.filter_field() + model: StrFilterLookup | None = strawberry_django.filter_field() diff --git a/netbox/core/tests/test_api.py b/netbox/core/tests/test_api.py index 2a159f6bd..81f16e3e7 100644 --- a/netbox/core/tests/test_api.py +++ b/netbox/core/tests/test_api.py @@ -12,7 +12,7 @@ from rq.registry import FailedJobRegistry, StartedJobRegistry from users.constants import TOKEN_PREFIX from users.models import Token -from utilities.testing import APITestCase, APIViewTestCases, TestCase +from utilities.testing import APITestCase, APIViewTestCases, GraphQLQueryTest, TestCase from utilities.testing.mixins import RQQueueTestMixin from utilities.testing.utils import disable_logging @@ -39,12 +39,49 @@ class DataSourceTestCase(APIViewTestCases.APIViewTestCase): @classmethod def setUpTestData(cls): data_sources = ( - DataSource(name='Data Source 1', type='local', source_url='file:///var/tmp/source1/'), + DataSource( + name='Data Source 1', type='local', source_url='file:///var/tmp/source1/', + parameters={ + 'sync_date': '2024-01-01', + 'sync_datetime': '2024-01-01T12:30:00+00:00', + 'sync_time': '12:30:00', + }, + ), DataSource(name='Data Source 2', type='local', source_url='file:///var/tmp/source2/'), DataSource(name='Data Source 3', type='local', source_url='file:///var/tmp/source3/'), ) DataSource.objects.bulk_create(data_sources) + cls.graphql_query_tests = ( + GraphQLQueryTest( + name='parameters_json_date_lookup', + query=( + '{ data_source_list(filters: {parameters: ' + '{path: "sync_date", lookup: {date_lookup: {exact: "2024-01-01"}}}}) ' + '{ id } }' + ), + assert_result=cls.assert_only_source_1, + ), + GraphQLQueryTest( + name='parameters_json_datetime_lookup', + query=( + '{ data_source_list(filters: {parameters: ' + '{path: "sync_datetime", lookup: {datetime_lookup: {exact: "2024-01-01T12:30:00+00:00"}}}}) ' + '{ id } }' + ), + assert_result=cls.assert_only_source_1, + ), + GraphQLQueryTest( + name='parameters_json_time_lookup', + query=( + '{ data_source_list(filters: {parameters: ' + '{path: "sync_time", lookup: {time_lookup: {exact: "12:30:00"}}}}) ' + '{ id } }' + ), + assert_result=cls.assert_only_source_1, + ), + ) + cls.create_data = [ { 'name': 'Data Source 4', @@ -63,6 +100,11 @@ class DataSourceTestCase(APIViewTestCases.APIViewTestCase): }, ] + def assert_only_source_1(self, data): + """The JSON lookup returns exactly the source carrying the matching value.""" + ids = sorted(result['id'] for result in data['data_source_list']) + self.assertEqual(ids, [str(DataSource.objects.get(name='Data Source 1').pk)]) + class DataFileTestCase( APIViewTestCases.GetObjectViewTestCase, diff --git a/netbox/dcim/graphql/filter_mixins.py b/netbox/dcim/graphql/filter_mixins.py index 07fa34616..fb5a21a0b 100644 --- a/netbox/dcim/graphql/filter_mixins.py +++ b/netbox/dcim/graphql/filter_mixins.py @@ -66,9 +66,9 @@ class ComponentModelFilterMixin: ) device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() device_id: ID | None = strawberry_django.filter_field() - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - label: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + label: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() @dataclass @@ -96,9 +96,9 @@ class ComponentTemplateFilterMixin: strawberry_django.filter_field() ) device_type_id: ID | None = strawberry_django.filter_field() - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - label: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + label: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() @dataclass diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py index 02fb3ef64..09ba07acd 100644 --- a/netbox/dcim/graphql/filters.py +++ b/netbox/dcim/graphql/filters.py @@ -116,7 +116,7 @@ __all__ = ( @strawberry_django.filter_type(models.CableBundle, lookups=True) class CableBundleFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.Cable, lookups=True) @@ -127,7 +127,7 @@ class CableFilter(TenancyFilterMixin, PrimaryModelFilter): status: BaseFilterLookup[Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - label: StrFilterLookup[str] | None = strawberry_django.filter_field() + label: StrFilterLookup | None = strawberry_django.filter_field() color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -223,9 +223,9 @@ class DeviceFilter( platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - serial: StrFilterLookup[str] | None = strawberry_django.filter_field() - asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + serial: StrFilterLookup | None = strawberry_django.filter_field() + asset_tag: StrFilterLookup | None = strawberry_django.filter_field() site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() site_id: ID | None = strawberry_django.filter_field() location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -353,7 +353,7 @@ class InventoryItemTemplateFilter(ComponentTemplateFilterMixin, ChangeLoggedMode strawberry_django.filter_field() ) manufacturer_id: ID | None = strawberry_django.filter_field() - part_id: StrFilterLookup[str] | None = strawberry_django.filter_field() + part_id: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.DeviceRole, lookups=True) @@ -370,13 +370,13 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, WeightFilterMixin, PrimaryMod strawberry_django.filter_field() ) manufacturer_id: ID | None = strawberry_django.filter_field() - model: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() + model: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() default_platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) default_platform_id: ID | None = strawberry_django.filter_field() - part_number: StrFilterLookup[str] | None = strawberry_django.filter_field() + part_number: StrFilterLookup | None = strawberry_django.filter_field() instances: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -493,7 +493,7 @@ class PortTemplateMappingFilter(BaseModelFilter): @strawberry_django.filter_type(models.MACAddress, lookups=True) class MACAddressFilter(PrimaryModelFilter): - mac_address: StrFilterLookup[str] | None = strawberry_django.filter_field() + mac_address: StrFilterLookup | None = strawberry_django.filter_field() assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -539,7 +539,7 @@ class InterfaceFilter( duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - wwn: StrFilterLookup[str] | None = strawberry_django.filter_field() + wwn: StrFilterLookup | None = strawberry_django.filter_field() parent: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -659,9 +659,9 @@ class InventoryItemFilter(ComponentModelFilterMixin, NetBoxModelFilter): strawberry_django.filter_field() ) manufacturer_id: ID | None = strawberry_django.filter_field() - part_id: StrFilterLookup[str] | None = strawberry_django.filter_field() - serial: StrFilterLookup[str] | None = strawberry_django.filter_field() - asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field() + part_id: StrFilterLookup | None = strawberry_django.filter_field() + serial: StrFilterLookup | None = strawberry_django.filter_field() + asset_tag: StrFilterLookup | None = strawberry_django.filter_field() discovered: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -679,7 +679,7 @@ class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilt status: BaseFilterLookup[Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - facility: StrFilterLookup[str] | None = strawberry_django.filter_field() + facility: StrFilterLookup | None = strawberry_django.filter_field() prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -708,8 +708,8 @@ class ModuleFilter(ConfigContextFilterMixin, PrimaryModelFilter): status: BaseFilterLookup[Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - serial: StrFilterLookup[str] | None = strawberry_django.filter_field() - asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field() + serial: StrFilterLookup | None = strawberry_django.filter_field() + asset_tag: StrFilterLookup | None = strawberry_django.filter_field() consoleports: Annotated['ConsolePortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field(name='console_ports') ) @@ -748,19 +748,19 @@ class ModuleBayFilter(ModularComponentFilterMixin, NetBoxModelFilter): strawberry_django.filter_field() ) parent_id: ID | None = strawberry_django.filter_field() - position: StrFilterLookup[str] | None = strawberry_django.filter_field() + position: StrFilterLookup | None = strawberry_django.filter_field() enabled: FilterLookup[bool] | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.ModuleBayTemplate, lookups=True) class ModuleBayTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter): - position: StrFilterLookup[str] | None = strawberry_django.filter_field() + position: StrFilterLookup | None = strawberry_django.filter_field() enabled: FilterLookup[bool] | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.ModuleTypeProfile, lookups=True) class ModuleTypeProfileFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.ModuleType, lookups=True) @@ -773,8 +773,8 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, WeightFilterMixin, PrimaryMod strawberry_django.filter_field() ) profile_id: ID | None = strawberry_django.filter_field() - model: StrFilterLookup[str] | None = strawberry_django.filter_field() - part_number: StrFilterLookup[str] | None = strawberry_django.filter_field() + model: StrFilterLookup | None = strawberry_django.filter_field() + part_number: StrFilterLookup | None = strawberry_django.filter_field() instances: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -831,7 +831,7 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM power_panel_id: ID | None = strawberry_django.filter_field() rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() rack_id: ID | None = strawberry_django.filter_field() - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() status: BaseFilterLookup[Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -902,7 +902,7 @@ class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryMo location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.PowerPort, lookups=True) @@ -940,8 +940,8 @@ class RackTypeFilter(ImageAttachmentFilterMixin, RackFilterMixin, WeightFilterMi strawberry_django.filter_field() ) manufacturer_id: ID | None = strawberry_django.filter_field() - model: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() + model: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | 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() @@ -962,8 +962,8 @@ class RackFilter( strawberry_django.filter_field() ) rack_type_id: ID | None = strawberry_django.filter_field() - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - facility_id: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + facility_id: StrFilterLookup | None = strawberry_django.filter_field() site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() site_id: ID | None = strawberry_django.filter_field() location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -981,8 +981,8 @@ class RackFilter( ) role: Annotated['RackRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() role_id: ID | None = strawberry_django.filter_field() - serial: StrFilterLookup[str] | None = strawberry_django.filter_field() - asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field() + serial: StrFilterLookup | None = strawberry_django.filter_field() + asset_tag: StrFilterLookup | None = strawberry_django.filter_field() airflow: BaseFilterLookup[Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -1006,7 +1006,7 @@ class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilter): unit_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field() user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() user_id: ID | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() status: BaseFilterLookup[Annotated['RackReservationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -1057,8 +1057,8 @@ class RegionFilter(ContactFilterMixin, NestedGroupModelFilter): @strawberry_django.filter_type(models.Site, lookups=True) class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() status: BaseFilterLookup[Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -1072,11 +1072,11 @@ class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMi group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - facility: StrFilterLookup[str] | None = strawberry_django.filter_field() + facility: StrFilterLookup | None = strawberry_django.filter_field() asns: Annotated['ASNFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() - time_zone: StrFilterLookup[str] | None = strawberry_django.filter_field() - physical_address: StrFilterLookup[str] | None = strawberry_django.filter_field() - shipping_address: StrFilterLookup[str] | None = strawberry_django.filter_field() + time_zone: StrFilterLookup | None = strawberry_django.filter_field() + physical_address: StrFilterLookup | None = strawberry_django.filter_field() + shipping_address: StrFilterLookup | None = strawberry_django.filter_field() latitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -1105,8 +1105,8 @@ class SiteGroupFilter(ContactFilterMixin, NestedGroupModelFilter): class VirtualChassisFilter(PrimaryModelFilter): master: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() master_id: ID | None = strawberry_django.filter_field() - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - domain: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + domain: StrFilterLookup | None = strawberry_django.filter_field() members: ( Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None ) = strawberry_django.filter_field() @@ -1117,7 +1117,7 @@ class VirtualChassisFilter(PrimaryModelFilter): class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilter): device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() device_id: ID | None = strawberry_django.filter_field() - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() status: ( BaseFilterLookup[Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None ) = ( @@ -1134,7 +1134,7 @@ class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilter): strawberry_django.filter_field() ) primary_ip6_id: ID | None = strawberry_django.filter_field() - comments: StrFilterLookup[str] | None = strawberry_django.filter_field() + comments: StrFilterLookup | None = strawberry_django.filter_field() interfaces: ( Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None ) = strawberry_django.filter_field() diff --git a/netbox/extras/graphql/filters.py b/netbox/extras/graphql/filters.py index 2479d46d2..5b6e2eab6 100644 --- a/netbox/extras/graphql/filters.py +++ b/netbox/extras/graphql/filters.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import TYPE_CHECKING, Annotated import strawberry @@ -55,11 +54,11 @@ __all__ = ( @strawberry_django.filter_type(models.ConfigContext, lookups=True) class ConfigContextFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() is_active: FilterLookup[bool] | None = strawberry_django.filter_field() regions: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() @@ -112,22 +111,22 @@ class ConfigContextFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter): @strawberry_django.filter_type(models.ConfigContextProfile, lookups=True) class ConfigContextProfileFilter(SyncedDataFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() tags: Annotated['TagFilter', strawberry.lazy('extras.graphql.filters')] | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.ConfigTemplate, lookups=True) class ConfigTemplateFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() - template_code: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() + template_code: StrFilterLookup | None = strawberry_django.filter_field() environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - mime_type: StrFilterLookup[str] | None = strawberry_django.filter_field() - file_name: StrFilterLookup[str] | None = strawberry_django.filter_field() - file_extension: StrFilterLookup[str] | None = strawberry_django.filter_field() + mime_type: StrFilterLookup | None = strawberry_django.filter_field() + file_name: StrFilterLookup | None = strawberry_django.filter_field() + file_extension: StrFilterLookup | None = strawberry_django.filter_field() as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -142,10 +141,10 @@ class CustomFieldFilter(ChangeLoggedModelFilter): related_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - label: StrFilterLookup[str] | None = strawberry_django.filter_field() - group_name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + label: StrFilterLookup | None = strawberry_django.filter_field() + group_name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() required: FilterLookup[bool] | None = strawberry_django.filter_field() unique: FilterLookup[bool] | None = strawberry_django.filter_field() search_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -171,7 +170,7 @@ class CustomFieldFilter(ChangeLoggedModelFilter): validation_maximum: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - validation_regex: StrFilterLookup[str] | None = strawberry_django.filter_field() + validation_regex: StrFilterLookup | None = strawberry_django.filter_field() choice_set: Annotated['CustomFieldChoiceSetFilter', strawberry.lazy('extras.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -187,13 +186,13 @@ class CustomFieldFilter(ChangeLoggedModelFilter): strawberry_django.filter_field() ) is_cloneable: FilterLookup[bool] | None = strawberry_django.filter_field() - comments: StrFilterLookup[str] | None = strawberry_django.filter_field() + comments: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.CustomFieldChoiceSet, lookups=True) class CustomFieldChoiceSetFilter(ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() base_choices: ( BaseFilterLookup[Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')]] | None ) = ( @@ -234,14 +233,14 @@ class CustomFieldChoiceSetFilter(ChangeLoggedModelFilter): @strawberry_django.filter_type(models.CustomLink, lookups=True) class CustomLinkFilter(ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - link_text: StrFilterLookup[str] | None = strawberry_django.filter_field() - link_url: StrFilterLookup[str] | None = strawberry_django.filter_field() + link_text: StrFilterLookup | None = strawberry_django.filter_field() + link_url: StrFilterLookup | None = strawberry_django.filter_field() weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - group_name: StrFilterLookup[str] | None = strawberry_django.filter_field() + group_name: StrFilterLookup | None = strawberry_django.filter_field() button_class: ( BaseFilterLookup[Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')]] | None ) = ( @@ -252,15 +251,15 @@ class CustomLinkFilter(ChangeLoggedModelFilter): @strawberry_django.filter_type(models.ExportTemplate, lookups=True) class ExportTemplateFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() - template_code: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() + template_code: StrFilterLookup | None = strawberry_django.filter_field() environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - mime_type: StrFilterLookup[str] | None = strawberry_django.filter_field() - file_name: StrFilterLookup[str] | None = strawberry_django.filter_field() - file_extension: StrFilterLookup[str] | None = strawberry_django.filter_field() + mime_type: StrFilterLookup | None = strawberry_django.filter_field() + file_name: StrFilterLookup | None = strawberry_django.filter_field() + file_extension: StrFilterLookup | None = strawberry_django.filter_field() as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -276,7 +275,7 @@ class ImageAttachmentFilter(ChangeLoggedModelFilter): image_width: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.JournalEntry, lookups=True) @@ -292,13 +291,13 @@ class JournalEntryFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedM kind: BaseFilterLookup[Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - comments: StrFilterLookup[str] | None = strawberry_django.filter_field() + comments: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.Notification, lookups=True) class NotificationFilter(BaseModelFilter): - created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - read: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + created: DatetimeFilterLookup | None = strawberry_django.filter_field() + read: DatetimeFilterLookup | None = strawberry_django.filter_field() user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() user_id: ID | None = strawberry_django.filter_field() object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( @@ -306,23 +305,23 @@ class NotificationFilter(BaseModelFilter): ) object_type_id: ID | None = strawberry_django.filter_field() object_id: ID | None = strawberry_django.filter_field() - object_repr: StrFilterLookup[str] | None = strawberry_django.filter_field() - event_type: StrFilterLookup[str] | None = strawberry_django.filter_field() + object_repr: StrFilterLookup | None = strawberry_django.filter_field() + event_type: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.NotificationGroup, lookups=True) class NotificationGroupFilter(ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() users: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.SavedFilter, lookups=True) class SavedFilterFilter(ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() user_id: ID | None = strawberry_django.filter_field() weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -337,7 +336,7 @@ class SavedFilterFilter(ChangeLoggedModelFilter): @strawberry_django.filter_type(models.Subscription, lookups=True) class SubscriptionFilter(BaseModelFilter): - created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + created: DatetimeFilterLookup | None = strawberry_django.filter_field() user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() user_id: ID | None = strawberry_django.filter_field() object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( @@ -349,8 +348,8 @@ class SubscriptionFilter(BaseModelFilter): @strawberry_django.filter_type(models.TableConfig, lookups=True) class TableConfigFilter(ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() user_id: ID | None = strawberry_django.filter_field() weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -362,30 +361,30 @@ class TableConfigFilter(ChangeLoggedModelFilter): @strawberry_django.filter_type(models.Tag, lookups=True) class TagFilter(ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.Webhook, lookups=True) class WebhookFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() - payload_url: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() + payload_url: StrFilterLookup | None = strawberry_django.filter_field() http_method: ( BaseFilterLookup[Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) - http_content_type: StrFilterLookup[str] | None = strawberry_django.filter_field() - additional_headers: StrFilterLookup[str] | None = strawberry_django.filter_field() - body_template: StrFilterLookup[str] | None = strawberry_django.filter_field() - secret: StrFilterLookup[str] | None = strawberry_django.filter_field() + http_content_type: StrFilterLookup | None = strawberry_django.filter_field() + additional_headers: StrFilterLookup | None = strawberry_django.filter_field() + body_template: StrFilterLookup | None = strawberry_django.filter_field() + secret: StrFilterLookup | None = strawberry_django.filter_field() ssl_verification: FilterLookup[bool] | None = strawberry_django.filter_field() - ca_file_path: StrFilterLookup[str] | None = strawberry_django.filter_field() + ca_file_path: StrFilterLookup | None = strawberry_django.filter_field() events: Annotated['EventRuleFilter', strawberry.lazy('extras.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -393,8 +392,8 @@ class WebhookFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelF @strawberry_django.filter_type(models.EventRule, lookups=True) class EventRuleFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() event_types: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -413,4 +412,4 @@ class EventRuleFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedMode action_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - comments: StrFilterLookup[str] | None = strawberry_django.filter_field() + comments: StrFilterLookup | None = strawberry_django.filter_field() diff --git a/netbox/ipam/graphql/filters.py b/netbox/ipam/graphql/filters.py index f3c6637fa..7d16c0fe6 100644 --- a/netbox/ipam/graphql/filters.py +++ b/netbox/ipam/graphql/filters.py @@ -1,4 +1,3 @@ -from datetime import date from typing import TYPE_CHECKING, Annotated import netaddr @@ -72,8 +71,8 @@ class ASNFilter(TenancyFilterMixin, PrimaryModelFilter): @strawberry_django.filter_type(models.ASNRange, lookups=True) class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() rir_id: ID | None = strawberry_django.filter_field() start: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -86,10 +85,10 @@ class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter): @strawberry_django.filter_type(models.Aggregate, lookups=True) class AggregateFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): - prefix: StrFilterLookup[str] | None = strawberry_django.filter_field() + prefix: StrFilterLookup | None = strawberry_django.filter_field() rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() rir_id: ID | None = strawberry_django.filter_field() - date_added: DateFilterLookup[date] | None = strawberry_django.filter_field() + date_added: DateFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_field() def contains(self, value: list[str], prefix) -> Q: @@ -122,14 +121,14 @@ class FHRPGroupFilter(PrimaryModelFilter): group_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() protocol: BaseFilterLookup[Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) auth_type: BaseFilterLookup[Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - auth_key: StrFilterLookup[str] | None = strawberry_django.filter_field() + auth_key: StrFilterLookup | None = strawberry_django.filter_field() ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -140,7 +139,7 @@ class FHRPGroupAssignmentFilter(ChangeLoggedModelFilter): interface_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) - interface_id: StrFilterLookup[str] | None = strawberry_django.filter_field() + interface_id: StrFilterLookup | None = strawberry_django.filter_field() group: Annotated['FHRPGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -176,7 +175,7 @@ class FHRPGroupAssignmentFilter(ChangeLoggedModelFilter): @strawberry_django.filter_type(models.IPAddress, lookups=True) class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): - address: StrFilterLookup[str] | None = strawberry_django.filter_field() + address: StrFilterLookup | None = strawberry_django.filter_field() vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vrf_id: ID | None = strawberry_django.filter_field() status: BaseFilterLookup[Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( @@ -197,7 +196,7 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter strawberry_django.filter_field() ) nat_outside_id: ID | None = strawberry_django.filter_field() - dns_name: StrFilterLookup[str] | None = strawberry_django.filter_field() + dns_name: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_field() def assigned(self, value: bool, prefix) -> Q: @@ -227,8 +226,8 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter @strawberry_django.filter_type(models.IPRange, lookups=True) class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): - start_address: StrFilterLookup[str] | None = strawberry_django.filter_field() - end_address: StrFilterLookup[str] | None = strawberry_django.filter_field() + start_address: StrFilterLookup | None = strawberry_django.filter_field() + end_address: StrFilterLookup | None = strawberry_django.filter_field() size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -281,7 +280,7 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): @strawberry_django.filter_type(models.Prefix, lookups=True) class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, PrimaryModelFilter): - prefix: StrFilterLookup[str] | None = strawberry_django.filter_field() + prefix: StrFilterLookup | None = strawberry_django.filter_field() vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vrf_id: ID | None = strawberry_django.filter_field() vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @@ -330,7 +329,7 @@ class RoleFilter(OrganizationalModelFilter): @strawberry_django.filter_type(models.RouteTarget, lookups=True) class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() importing_vrfs: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -347,7 +346,7 @@ class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilter): @strawberry_django.filter_type(models.Service, lookups=True) class ServiceFilter(ContactFilterMixin, ServiceFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -359,7 +358,7 @@ class ServiceFilter(ContactFilterMixin, ServiceFilterMixin, PrimaryModelFilter): @strawberry_django.filter_type(models.ServiceTemplate, lookups=True) class ServiceTemplateFilter(ServiceFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.VLAN, lookups=True) @@ -373,7 +372,7 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilter): vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() status: BaseFilterLookup[Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -404,7 +403,7 @@ class VLANGroupFilter(ScopedFilterMixin, OrganizationalModelFilter): @strawberry_django.filter_type(models.VLANTranslationPolicy, lookups=True) class VLANTranslationPolicyFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.VLANTranslationRule, lookups=True) @@ -413,7 +412,7 @@ class VLANTranslationRuleFilter(NetBoxModelFilter): strawberry_django.filter_field() ) policy_id: ID | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() local_vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -424,8 +423,8 @@ class VLANTranslationRuleFilter(NetBoxModelFilter): @strawberry_django.filter_type(models.VRF, lookups=True) class VRFFilter(TenancyFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - rd: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + rd: StrFilterLookup | None = strawberry_django.filter_field() enforce_unique: FilterLookup[bool] | None = strawberry_django.filter_field() import_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() diff --git a/netbox/netbox/graphql/filter_lookups.py b/netbox/netbox/graphql/filter_lookups.py index 243dfb4e8..d3da1239e 100644 --- a/netbox/netbox/graphql/filter_lookups.py +++ b/netbox/netbox/graphql/filter_lookups.py @@ -11,12 +11,9 @@ from strawberry.directive import DirectiveValue from strawberry.types import Info from strawberry_django import ( ComparisonFilterLookup, - DateFilterLookup, - DatetimeFilterLookup, FilterLookup, RangeLookup, StrFilterLookup, - TimeFilterLookup, process_filters, ) @@ -39,16 +36,58 @@ T = TypeVar('T') SKIP_MSG = 'Filter will be skipped on `null` value' +# These JSON lookup types intentionally mirror the legacy DateFilterLookup[str], +# TimeFilterLookup[str], and DatetimeFilterLookup[str] schema. JSON values are +# string-backed, so the concrete strawberry-django date/time lookup classes +# (which now ignore type parameters and warn) are deliberately not used here. +@strawberry.input(name='StrDateFilterLookup') +class JSONDateFilterLookup(ComparisonFilterLookup[str]): + year: ComparisonFilterLookup[int] | None = strawberry.UNSET + month: ComparisonFilterLookup[int] | None = strawberry.UNSET + day: ComparisonFilterLookup[int] | None = strawberry.UNSET + week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET + iso_week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET + week: ComparisonFilterLookup[int] | None = strawberry.UNSET + iso_year: ComparisonFilterLookup[int] | None = strawberry.UNSET + quarter: ComparisonFilterLookup[int] | None = strawberry.UNSET + + +@strawberry.input(name='StrTimeFilterLookup') +class JSONTimeFilterLookup(ComparisonFilterLookup[str]): + hour: ComparisonFilterLookup[int] | None = strawberry.UNSET + minute: ComparisonFilterLookup[int] | None = strawberry.UNSET + second: ComparisonFilterLookup[int] | None = strawberry.UNSET + date: ComparisonFilterLookup[int] | None = strawberry.UNSET + time: ComparisonFilterLookup[int] | None = strawberry.UNSET + + +@strawberry.input(name='StrDatetimeFilterLookup') +class JSONDatetimeFilterLookup(ComparisonFilterLookup[str]): + year: ComparisonFilterLookup[int] | None = strawberry.UNSET + month: ComparisonFilterLookup[int] | None = strawberry.UNSET + day: ComparisonFilterLookup[int] | None = strawberry.UNSET + week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET + iso_week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET + week: ComparisonFilterLookup[int] | None = strawberry.UNSET + iso_year: ComparisonFilterLookup[int] | None = strawberry.UNSET + quarter: ComparisonFilterLookup[int] | None = strawberry.UNSET + hour: ComparisonFilterLookup[int] | None = strawberry.UNSET + minute: ComparisonFilterLookup[int] | None = strawberry.UNSET + second: ComparisonFilterLookup[int] | None = strawberry.UNSET + date: ComparisonFilterLookup[int] | None = strawberry.UNSET + time: ComparisonFilterLookup[int] | None = strawberry.UNSET + + @strawberry.input(one_of=True, description='Lookup for JSON field. Only one of the lookup fields can be set.') class JSONLookup: - string_lookup: StrFilterLookup[str] | None = strawberry_django.filter_field() + string_lookup: StrFilterLookup | None = strawberry_django.filter_field() int_range_lookup: RangeLookup[int] | None = strawberry_django.filter_field() int_comparison_lookup: ComparisonFilterLookup[int] | None = strawberry_django.filter_field() float_range_lookup: RangeLookup[float] | None = strawberry_django.filter_field() float_comparison_lookup: ComparisonFilterLookup[float] | None = strawberry_django.filter_field() - date_lookup: DateFilterLookup[str] | None = strawberry_django.filter_field() - datetime_lookup: DatetimeFilterLookup[str] | None = strawberry_django.filter_field() - time_lookup: TimeFilterLookup[str] | None = strawberry_django.filter_field() + date_lookup: JSONDateFilterLookup | None = strawberry_django.filter_field() + datetime_lookup: JSONDatetimeFilterLookup | None = strawberry_django.filter_field() + time_lookup: JSONTimeFilterLookup | None = strawberry_django.filter_field() boolean_lookup: FilterLookup[bool] | None = strawberry_django.filter_field() def get_filter(self): diff --git a/netbox/netbox/graphql/filter_mixins.py b/netbox/netbox/graphql/filter_mixins.py index 94b836f06..c33e3e8b9 100644 --- a/netbox/netbox/graphql/filter_mixins.py +++ b/netbox/netbox/graphql/filter_mixins.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from datetime import datetime from typing import TYPE_CHECKING, Annotated, TypeVar import strawberry @@ -48,9 +47,9 @@ class SyncedDataFilterMixin: strawberry_django.filter_field() ) data_file_id: FilterLookup[int] | None = strawberry_django.filter_field() - data_path: StrFilterLookup[str] | None = strawberry_django.filter_field() + data_path: StrFilterLookup | None = strawberry_django.filter_field() auto_sync_enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - data_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + data_synced: DatetimeFilterLookup | None = strawberry_django.filter_field() @dataclass diff --git a/netbox/netbox/graphql/filters.py b/netbox/netbox/graphql/filters.py index a372d7bc0..c6241e226 100644 --- a/netbox/netbox/graphql/filters.py +++ b/netbox/netbox/graphql/filters.py @@ -42,21 +42,21 @@ class NetBoxModelFilter( @dataclass class NestedGroupModelFilter(NetBoxModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() parent_id: ID | None = strawberry_django.filter_field() @dataclass class OrganizationalModelFilter(NetBoxModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() - comments: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() + comments: StrFilterLookup | None = strawberry_django.filter_field() @dataclass class PrimaryModelFilter(NetBoxModelFilter): - description: StrFilterLookup[str] | None = strawberry_django.filter_field() - comments: StrFilterLookup[str] | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() + comments: StrFilterLookup | None = strawberry_django.filter_field() diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index e14d9c7ff..0b711348b 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -1,4 +1,5 @@ import json +import re import strawberry from django.contrib.contenttypes.models import ContentType @@ -12,7 +13,7 @@ from dcim.choices import LocationStatusChoices from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Site, VirtualChassis from extras.models import TableConfig, Tag from netbox.graphql.scalars import BigInt, BigIntScalar -from netbox.graphql.schema import Query, get_schema_extensions +from netbox.graphql.schema import Query, get_schema_extensions, schema from utilities.tables import get_table_for_model from utilities.testing import APITestCase, TestCase, disable_warnings @@ -90,6 +91,30 @@ class GraphQLTestCase(TestCase): with disable_warnings('django.request'): self.assertHttpStatus(response, 302) # Redirect to login page + def test_json_lookup_schema_is_string_backed(self): + """JSONLookup date/time lookups keep the legacy string-backed input types and fields.""" + sdl = schema.as_str() + + def input_block(name): + match = re.search(rf'^input {re.escape(name)}\b.*?^\}}', sdl, re.DOTALL | re.MULTILINE) + self.assertIsNotNone(match, f'{name} not found in schema') + return match.group(0) + + # JSONLookup points at the legacy string-backed lookup type names + json_lookup = input_block('JSONLookup') + self.assertIn('date_lookup: StrDateFilterLookup', json_lookup) + self.assertIn('datetime_lookup: StrDatetimeFilterLookup', json_lookup) + self.assertIn('time_lookup: StrTimeFilterLookup', json_lookup) + + # Value fields are string-backed, not Date/DateTime/Time scalars + self.assertIn('exact: String', input_block('StrDateFilterLookup')) + + # Legacy date/time sub-lookups remain integer comparison lookups + for name in ('StrTimeFilterLookup', 'StrDatetimeFilterLookup'): + block = input_block(name) + self.assertIn('date: IntComparisonFilterLookup', block) + self.assertIn('time: IntComparisonFilterLookup', block) + class GraphQLAPITestCase(APITestCase): diff --git a/netbox/tenancy/graphql/filters.py b/netbox/tenancy/graphql/filters.py index 7b340c4fc..0a570c980 100644 --- a/netbox/tenancy/graphql/filters.py +++ b/netbox/tenancy/graphql/filters.py @@ -60,8 +60,8 @@ __all__ = ( @strawberry_django.filter_type(models.Tenant, lookups=True) class TenantFilter(ContactFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() group: Annotated['TenantGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -153,12 +153,12 @@ class TenantGroupFilter(OrganizationalModelFilter): @strawberry_django.filter_type(models.Contact, lookups=True) class ContactFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - title: StrFilterLookup[str] | None = strawberry_django.filter_field() - phone: StrFilterLookup[str] | None = strawberry_django.filter_field() - email: StrFilterLookup[str] | None = strawberry_django.filter_field() - address: StrFilterLookup[str] | None = strawberry_django.filter_field() - link: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + title: StrFilterLookup | None = strawberry_django.filter_field() + phone: StrFilterLookup | None = strawberry_django.filter_field() + email: StrFilterLookup | None = strawberry_django.filter_field() + address: StrFilterLookup | None = strawberry_django.filter_field() + link: StrFilterLookup | None = strawberry_django.filter_field() groups: Annotated['ContactGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( strawberry_django.filter_field() ) diff --git a/netbox/users/graphql/filters.py b/netbox/users/graphql/filters.py index c3d6d6f25..6ff40597c 100644 --- a/netbox/users/graphql/filters.py +++ b/netbox/users/graphql/filters.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated import strawberry @@ -18,27 +17,27 @@ __all__ = ( @strawberry_django.filter_type(models.Group, lookups=True) class GroupFilter(BaseModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.User, lookups=True) class UserFilter(BaseModelFilter): - username: StrFilterLookup[str] | None = strawberry_django.filter_field() - first_name: StrFilterLookup[str] | None = strawberry_django.filter_field() - last_name: StrFilterLookup[str] | None = strawberry_django.filter_field() - email: StrFilterLookup[str] | None = strawberry_django.filter_field() + username: StrFilterLookup | None = strawberry_django.filter_field() + first_name: StrFilterLookup | None = strawberry_django.filter_field() + last_name: StrFilterLookup | None = strawberry_django.filter_field() + email: StrFilterLookup | None = strawberry_django.filter_field() is_superuser: FilterLookup[bool] | None = strawberry_django.filter_field() is_active: FilterLookup[bool] | None = strawberry_django.filter_field() - date_joined: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - last_login: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + date_joined: DatetimeFilterLookup | None = strawberry_django.filter_field() + last_login: DatetimeFilterLookup | None = strawberry_django.filter_field() groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.Owner, lookups=True) class OwnerFilter(BaseModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() group: Annotated['OwnerGroupFilter', strawberry.lazy('users.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -50,5 +49,5 @@ class OwnerFilter(BaseModelFilter): @strawberry_django.filter_type(models.OwnerGroup, lookups=True) class OwnerGroupFilter(BaseModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() diff --git a/netbox/virtualization/graphql/filter_mixins.py b/netbox/virtualization/graphql/filter_mixins.py index c1ff60568..5318b4bad 100644 --- a/netbox/virtualization/graphql/filter_mixins.py +++ b/netbox/virtualization/graphql/filter_mixins.py @@ -20,5 +20,5 @@ class VMComponentFilterMixin: strawberry_django.filter_field() ) virtual_machine_id: ID | None = strawberry_django.filter_field() - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - description: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + description: StrFilterLookup | None = strawberry_django.filter_field() diff --git a/netbox/virtualization/graphql/filters.py b/netbox/virtualization/graphql/filters.py index 4605c4424..c3f62fa1c 100644 --- a/netbox/virtualization/graphql/filters.py +++ b/netbox/virtualization/graphql/filters.py @@ -40,7 +40,7 @@ __all__ = ( @strawberry_django.filter_type(models.Cluster, lookups=True) class ClusterFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() type: Annotated['ClusterTypeFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -96,7 +96,7 @@ class VirtualMachineFilter( TenancyFilterMixin, PrimaryModelFilter, ): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() virtual_machine_type: ( Annotated['VirtualMachineTypeFilter', strawberry.lazy('virtualization.graphql.filters')] | None ) = strawberry_django.filter_field() @@ -138,7 +138,7 @@ class VirtualMachineFilter( disk: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - serial: StrFilterLookup[str] | None = strawberry_django.filter_field() + serial: StrFilterLookup | None = strawberry_django.filter_field() interface_count: FilterLookup[int] | None = strawberry_django.filter_field() virtual_disk_count: FilterLookup[int] | None = strawberry_django.filter_field() interfaces: Annotated['VMInterfaceFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( diff --git a/netbox/vpn/graphql/filters.py b/netbox/vpn/graphql/filters.py index 6b0f89f52..9b068859c 100644 --- a/netbox/vpn/graphql/filters.py +++ b/netbox/vpn/graphql/filters.py @@ -63,7 +63,7 @@ class TunnelTerminationFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLo @strawberry_django.filter_type(models.Tunnel, lookups=True) class TunnelFilter(TenancyFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() status: BaseFilterLookup[Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -89,7 +89,7 @@ class TunnelFilter(TenancyFilterMixin, PrimaryModelFilter): @strawberry_django.filter_type(models.IKEProposal, lookups=True) class IKEProposalFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() authentication_method: ( BaseFilterLookup[Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')]] | None ) = ( @@ -118,7 +118,7 @@ class IKEProposalFilter(PrimaryModelFilter): @strawberry_django.filter_type(models.IKEPolicy, lookups=True) class IKEPolicyFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() version: BaseFilterLookup[Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -128,12 +128,12 @@ class IKEPolicyFilter(PrimaryModelFilter): proposals: Annotated['IKEProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( strawberry_django.filter_field() ) - preshared_key: StrFilterLookup[str] | None = strawberry_django.filter_field() + preshared_key: StrFilterLookup | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.IPSecProposal, lookups=True) class IPSecProposalFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() encryption_algorithm: ( BaseFilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None ) = ( @@ -159,7 +159,7 @@ class IPSecProposalFilter(PrimaryModelFilter): @strawberry_django.filter_type(models.IPSecPolicy, lookups=True) class IPSecPolicyFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() proposals: Annotated['IPSecProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -170,7 +170,7 @@ class IPSecPolicyFilter(PrimaryModelFilter): @strawberry_django.filter_type(models.IPSecProfile, lookups=True) class IPSecProfileFilter(PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() mode: BaseFilterLookup[Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -186,8 +186,8 @@ class IPSecProfileFilter(PrimaryModelFilter): @strawberry_django.filter_type(models.L2VPN, lookups=True) class L2VPNFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): - name: StrFilterLookup[str] | None = strawberry_django.filter_field() - slug: StrFilterLookup[str] | None = strawberry_django.filter_field() + name: StrFilterLookup | None = strawberry_django.filter_field() + slug: StrFilterLookup | None = strawberry_django.filter_field() type: BaseFilterLookup[Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) diff --git a/netbox/wireless/graphql/filter_mixins.py b/netbox/wireless/graphql/filter_mixins.py index 9e928b3a5..0bad507e3 100644 --- a/netbox/wireless/graphql/filter_mixins.py +++ b/netbox/wireless/graphql/filter_mixins.py @@ -21,4 +21,4 @@ class WirelessAuthenticationFilterMixin: auth_cipher: Annotated['WirelessAuthCipherEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( strawberry_django.filter_field() ) - auth_psk: StrFilterLookup[str] | None = strawberry_django.filter_field() + auth_psk: StrFilterLookup | None = strawberry_django.filter_field() diff --git a/netbox/wireless/graphql/filters.py b/netbox/wireless/graphql/filters.py index 004d169ab..3bcef7725 100644 --- a/netbox/wireless/graphql/filters.py +++ b/netbox/wireless/graphql/filters.py @@ -38,7 +38,7 @@ class WirelessLANFilter( TenancyFilterMixin, PrimaryModelFilter ): - ssid: StrFilterLookup[str] | None = strawberry_django.filter_field() + ssid: StrFilterLookup | None = strawberry_django.filter_field() status: BaseFilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -65,7 +65,7 @@ class WirelessLinkFilter( strawberry_django.filter_field() ) interface_b_id: ID | None = strawberry_django.filter_field() - ssid: StrFilterLookup[str] | None = strawberry_django.filter_field() + ssid: StrFilterLookup | None = strawberry_django.filter_field() status: BaseFilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) diff --git a/requirements.txt b/requirements.txt index 374861243..61c35722f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ social-auth-app-django==5.9.0 social-auth-core==4.8.7 sorl-thumbnail==13.0.0 strawberry-graphql==0.316.0 -strawberry-graphql-django==0.85.0 +strawberry-graphql-django==0.86.1 svgwrite==1.4.3 tablib==3.9.0 tzdata==2026.2 From 61696c8633df42b19267d9acc92b1acf32e82d5b Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Tue, 16 Jun 2026 09:11:06 -0400 Subject: [PATCH 56/58] Closes #22427: Validate JSONFilter.path; add JSONStringLookup with regex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add _validate_json_path(): each __-separated path segment must match [A-Za-z0-9_][A-Za-z0-9_-]* (allows leading underscores per Jeremy's suggestion; ORM operator names like 'date'/'regex' are valid JSON keys and are not blocked — the trailing __ JSONFilter appends makes them key traversal steps, not ORM transforms) - Add JSONStringLookup: explicit string-filter type for JSONLookup. regex/i_regex are included (they offer no additional oracle power beyond starts_with, which is also present, per Jeremy's observation) - JSONFilter.filter() validates self.path and returns empty Q() on invalid input rather than passing untrusted user input to the ORM - 19 unit tests for path validation and JSONStringLookup field presence Co-Authored-By: Claude Sonnet 4.6 --- netbox/netbox/graphql/filter_lookups.py | 72 ++++++++++++++++++- netbox/netbox/tests/test_graphql.py | 94 +++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/graphql/filter_lookups.py b/netbox/netbox/graphql/filter_lookups.py index d3da1239e..3419931bf 100644 --- a/netbox/netbox/graphql/filter_lookups.py +++ b/netbox/netbox/graphql/filter_lookups.py @@ -1,3 +1,4 @@ +import re from enum import Enum from typing import Generic, TypeVar @@ -13,12 +14,46 @@ from strawberry_django import ( ComparisonFilterLookup, FilterLookup, RangeLookup, - StrFilterLookup, process_filters, ) from netbox.graphql.scalars import BigInt +# ------------------------------------------------------------------ +# JSON path validation (VM-323) +# ------------------------------------------------------------------ + +# Each segment of a JSON path may only contain alphanumerics, underscores, and +# hyphens. Hyphens are included because JSON keys commonly use them; leading +# underscores are permitted (e.g. _foo is a valid key name). +_JSON_PATH_SEGMENT_RE = re.compile(r'^[A-Za-z0-9_][A-Za-z0-9_-]*$') + + +def _validate_json_path(path: str) -> str: + """Validate a JSON traversal path for use in ORM lookups. + + Each ``__``-separated segment must match ``[A-Za-z0-9_][A-Za-z0-9_-]*``. + Raises ``ValueError`` on an empty path, empty segment, or segment with + disallowed characters. + + ORM operator names (``date``, ``regex``, etc.) are intentionally *not* + blocked here: ``JSONFilter.filter()`` always appends ``__`` to the path + before handing it to ``process_filters``, so a segment named ``regex`` + becomes another level of JSON key traversal (``data__key__regex__exact``), + not the ORM regex transform (``data__key__regex=…``). + """ + if not path: + raise ValueError("JSON path cannot be empty") + + for segment in path.split('__'): + if not segment: + raise ValueError("JSON path contains consecutive or trailing '__'") + if not _JSON_PATH_SEGMENT_RE.match(segment): + raise ValueError(f"Invalid JSON path segment: {segment!r}") + + return path + + __all__ = ( 'ArrayLookup', 'BigIntegerLookup', @@ -28,6 +63,8 @@ __all__ = ( 'IntegerLookup', 'IntegerRangeArrayLookup', 'JSONFilter', + 'JSONLookup', + 'JSONStringLookup', 'StringArrayLookup', 'TreeNodeFilter', ) @@ -78,9 +115,33 @@ class JSONDatetimeFilterLookup(ComparisonFilterLookup[str]): time: ComparisonFilterLookup[int] | None = strawberry.UNSET +@strawberry.input(description='String lookups for JSON field values.') +class JSONStringLookup: + """ + String-filter type for use inside JSONLookup. + + Equivalent to ``StrFilterLookup`` but defined explicitly so that the type + name remains stable and any future per-field restrictions are easy to add. + ``regex`` / ``i_regex`` are included: they provide no additional oracle + power beyond ``starts_with``, which is also present. + """ + exact: str | None = strawberry_django.filter_field() + i_exact: str | None = strawberry_django.filter_field() + contains: str | None = strawberry_django.filter_field() + i_contains: str | None = strawberry_django.filter_field() + starts_with: str | None = strawberry_django.filter_field() + i_starts_with: str | None = strawberry_django.filter_field() + ends_with: str | None = strawberry_django.filter_field() + i_ends_with: str | None = strawberry_django.filter_field() + in_: list[str] | None = strawberry_django.filter_field() + isnull: bool | None = strawberry_django.filter_field() + regex: str | None = strawberry_django.filter_field() + i_regex: str | None = strawberry_django.filter_field() + + @strawberry.input(one_of=True, description='Lookup for JSON field. Only one of the lookup fields can be set.') class JSONLookup: - string_lookup: StrFilterLookup | None = strawberry_django.filter_field() + string_lookup: JSONStringLookup | None = strawberry_django.filter_field() int_range_lookup: RangeLookup[int] | None = strawberry_django.filter_field() int_comparison_lookup: ComparisonFilterLookup[int] | None = strawberry_django.filter_field() float_range_lookup: RangeLookup[float] | None = strawberry_django.filter_field() @@ -158,7 +219,12 @@ class JSONFilter: if not filters: return queryset, Q() - json_path = f'{prefix}{self.path}__' + try: + safe_path = _validate_json_path(self.path) + except ValueError: + return queryset, Q() + + json_path = f'{prefix}{safe_path}__' return process_filters(filters=filters, queryset=queryset, info=info, prefix=json_path) diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index 0b711348b..e65aca00f 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -505,3 +505,97 @@ class GraphQLAPITestCase(APITestCase): data = json.loads(response.content) self.assertIn('errors', data) self.assertEqual(data['errors'][0]['message'], 'Cannot specify both `start` and `offset` in pagination.') + + +class JSONPathValidationTestCase(TestCase): + """Unit tests for _validate_json_path (VM-323 security fix).""" + + def setUp(self): + from netbox.graphql.filter_lookups import _validate_json_path + self.validate = _validate_json_path + + # --- Valid paths --- + + def test_single_key(self): + self.assertEqual(self.validate('key'), 'key') + + def test_nested_key(self): + self.assertEqual(self.validate('parent__child'), 'parent__child') + + def test_deeply_nested(self): + self.assertEqual(self.validate('a__b__c'), 'a__b__c') + + def test_key_with_underscores(self): + self.assertEqual(self.validate('my_key'), 'my_key') + + def test_key_with_hyphens(self): + self.assertEqual(self.validate('my-key'), 'my-key') + + def test_numeric_array_index(self): + self.assertEqual(self.validate('items__0'), 'items__0') + + def test_alphanumeric_segment(self): + self.assertEqual(self.validate('key123'), 'key123') + + def test_key_with_leading_underscore(self): + # JSON keys may start with underscore (e.g. _foo) + self.assertEqual(self.validate('_key'), '_key') + + def test_orm_operator_name_as_key(self): + # 'date', 'regex' etc. are valid JSON key names; the path validator + # must not block them. The ORM injection risk is neutralised by the + # trailing __ that JSONFilter always appends before process_filters. + self.assertEqual(self.validate('date'), 'date') + self.assertEqual(self.validate('key__regex'), 'key__regex') + self.assertEqual(self.validate('key__exact'), 'key__exact') + + # --- Invalid paths --- + + def test_rejects_empty_string(self): + with self.assertRaises(ValueError): + self.validate('') + + def test_rejects_all_underscores(self): + # '___' splits into segments ['', '', ''] via '__' — empty segments rejected + with self.assertRaises(ValueError): + self.validate('___') + + def test_accepts_trailing_single_underscore(self): + # A single trailing underscore is a valid JSON key character + self.assertEqual(self.validate('key_'), 'key_') + + def test_rejects_trailing_double_underscore(self): + with self.assertRaises(ValueError): + self.validate('key__') + + def test_rejects_leading_double_underscore(self): + with self.assertRaises(ValueError): + self.validate('__key') + + def test_rejects_consecutive_double_underscores(self): + with self.assertRaises(ValueError): + self.validate('key1____key2') + + def test_rejects_segment_starting_with_special_char(self): + with self.assertRaises(ValueError): + self.validate('$secret') + + def test_rejects_path_with_spaces(self): + with self.assertRaises(ValueError): + self.validate('key one') + + def test_rejects_path_with_dot(self): + with self.assertRaises(ValueError): + self.validate('key.subkey') + + +class JSONStringLookupTestCase(TestCase): + """Verify JSONStringLookup exposes the expected set of string operators.""" + + def test_string_operators_present(self): + from netbox.graphql.filter_lookups import JSONStringLookup + field_names = {f.name for f in JSONStringLookup.__strawberry_definition__.fields} + for expected in ('exact', 'i_exact', 'contains', 'i_contains', + 'starts_with', 'i_starts_with', 'ends_with', 'i_ends_with', + 'in_', 'isnull', 'regex', 'i_regex'): + self.assertIn(expected, field_names, f"{expected!r} must be present on JSONStringLookup") From 086b1cf34d647db5eb481819eb79da79add260f6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 16 Jun 2026 10:45:47 -0400 Subject: [PATCH 57/58] Fixes #22466: Fix test failure against SSL-enabled PosgtreSQL --- netbox/core/tests/test_api.py | 3 +-- netbox/core/tests/test_views.py | 3 +-- netbox/extras/tests/test_event_rules.py | 5 +++-- netbox/utilities/testing/mixins.py | 15 +++++++++++++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/netbox/core/tests/test_api.py b/netbox/core/tests/test_api.py index 81f16e3e7..332169f2c 100644 --- a/netbox/core/tests/test_api.py +++ b/netbox/core/tests/test_api.py @@ -321,9 +321,8 @@ class BackgroundTaskTestCase(RQQueueTestMixin, TestCase): # Enqueue & run a job that will fail queue = get_queue('default') job = queue.enqueue(self.dummy_job_failing) - worker = get_worker('default') with disable_logging(): - worker.work(burst=True) + self.run_rq_jobs('default') self.assertTrue(job.is_failed) url = reverse('core-api:rqtask-requeue', args=[job.id]) diff --git a/netbox/core/tests/test_views.py b/netbox/core/tests/test_views.py index b48bc89e7..dfa2c9ed8 100644 --- a/netbox/core/tests/test_views.py +++ b/netbox/core/tests/test_views.py @@ -386,9 +386,8 @@ class BackgroundTaskTestCase(RQQueueTestMixin, TestCase): # Enqueue & run a job that will fail job = queue.enqueue(self.dummy_job_failing) - worker = get_worker('default') with disable_logging(): - worker.work(burst=True) + self.run_rq_jobs('default') self.assertTrue(job.is_failed) # Re-enqueue the failed job and check that its status has been reset diff --git a/netbox/extras/tests/test_event_rules.py b/netbox/extras/tests/test_event_rules.py index f2d753be5..cc0f0bd4d 100644 --- a/netbox/extras/tests/test_event_rules.py +++ b/netbox/extras/tests/test_event_rules.py @@ -27,9 +27,10 @@ from extras.signals import process_job_end_event_rules from extras.webhooks import generate_signature, send_webhook from netbox.context_managers import event_tracking from utilities.testing import APITestCase, create_test_device +from utilities.testing.mixins import RQQueueTestMixin -class EventRuleTestCase(APITestCase): +class EventRuleTestCase(RQQueueTestMixin, APITestCase): def setUp(self): super().setUp() @@ -741,7 +742,7 @@ class EventRuleTestCase(APITestCase): # silence rqworker (cleaner output) and trigger job execution logging.getLogger('rq.worker').setLevel(logging.ERROR) - django_rq.get_worker().work(burst=True) + self.run_rq_jobs('default') # Assert that our script was executed without any errors script_job.refresh_from_db() diff --git a/netbox/utilities/testing/mixins.py b/netbox/utilities/testing/mixins.py index 408d5d644..19d1b1df3 100644 --- a/netbox/utilities/testing/mixins.py +++ b/netbox/utilities/testing/mixins.py @@ -1,4 +1,6 @@ from django_rq import get_queue +from django_rq.workers import get_worker +from rq import SimpleWorker __all__ = ( 'RQQueueTestMixin', @@ -16,6 +18,19 @@ class RQQueueTestMixin: for queue_name in cls.rq_queue_names: get_queue(queue_name).connection.flushall() + def run_rq_jobs(self, *queue_names, burst=True): + """ + Process queued RQ jobs synchronously for the given queue(s) (defaulting to 'default'). + + Uses a non-forking SimpleWorker: the default RQ worker forks a work horse which would + inherit the test's open database connection. Two processes sharing one connection + corrupts it — on an SSL-encrypted connection this surfaces as "bad record mac" and + closes the connection for every subsequent test. SimpleWorker runs jobs in-process, + so the connection is never shared. + """ + worker = get_worker(*(queue_names or ('default',)), worker_class=SimpleWorker) + worker.work(burst=burst) + def setUp(self): super().setUp() From 0c26f973ffb56a945391fd93bae8542bbcca8e37 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 16 Jun 2026 10:25:04 -0400 Subject: [PATCH 58/58] Release v4.6.3 --- .../ISSUE_TEMPLATE/01-feature_request.yaml | 2 +- .github/ISSUE_TEMPLATE/02-bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/03-performance.yaml | 2 +- contrib/generated_schema.json | 8 + contrib/openapi.json | 34467 +++++++++++++++- docs/release-notes/version-4.6.md | 46 + netbox/project-static/dist/netbox.js | 2 +- netbox/project-static/dist/netbox.js.map | 2 +- netbox/project-static/package.json | 12 +- netbox/project-static/yarn.lock | 418 +- netbox/release.yaml | 4 +- netbox/translations/cs/LC_MESSAGES/django.mo | Bin 278554 -> 280098 bytes netbox/translations/cs/LC_MESSAGES/django.po | 1410 +- netbox/translations/da/LC_MESSAGES/django.mo | Bin 269977 -> 271471 bytes netbox/translations/da/LC_MESSAGES/django.po | 1409 +- netbox/translations/de/LC_MESSAGES/django.mo | Bin 284006 -> 285668 bytes netbox/translations/de/LC_MESSAGES/django.po | 1417 +- netbox/translations/es/LC_MESSAGES/django.mo | Bin 286292 -> 287927 bytes netbox/translations/es/LC_MESSAGES/django.po | 1418 +- netbox/translations/fr/LC_MESSAGES/django.mo | Bin 288783 -> 290433 bytes netbox/translations/fr/LC_MESSAGES/django.po | 1418 +- netbox/translations/it/LC_MESSAGES/django.mo | Bin 283622 -> 285212 bytes netbox/translations/it/LC_MESSAGES/django.po | 1416 +- netbox/translations/ja/LC_MESSAGES/django.mo | Bin 306089 -> 307761 bytes netbox/translations/ja/LC_MESSAGES/django.po | 1414 +- netbox/translations/lv/LC_MESSAGES/django.mo | Bin 277721 -> 279279 bytes netbox/translations/lv/LC_MESSAGES/django.po | 1429 +- netbox/translations/nl/LC_MESSAGES/django.mo | Bin 279065 -> 280653 bytes netbox/translations/nl/LC_MESSAGES/django.po | 1416 +- netbox/translations/pl/LC_MESSAGES/django.mo | Bin 281756 -> 283358 bytes netbox/translations/pl/LC_MESSAGES/django.po | 1415 +- netbox/translations/pt/LC_MESSAGES/django.mo | Bin 281240 -> 282839 bytes netbox/translations/pt/LC_MESSAGES/django.po | 1417 +- netbox/translations/ru/LC_MESSAGES/django.mo | Bin 362510 -> 364558 bytes netbox/translations/ru/LC_MESSAGES/django.po | 1413 +- netbox/translations/tr/LC_MESSAGES/django.mo | Bin 274510 -> 276047 bytes netbox/translations/tr/LC_MESSAGES/django.po | 1411 +- netbox/translations/uk/LC_MESSAGES/django.mo | Bin 360681 -> 362700 bytes netbox/translations/uk/LC_MESSAGES/django.po | 1414 +- netbox/translations/zh/LC_MESSAGES/django.mo | Bin 254139 -> 255561 bytes netbox/translations/zh/LC_MESSAGES/django.po | 1403 +- pyproject.toml | 2 +- requirements.txt | 14 +- 43 files changed, 45265 insertions(+), 10936 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/01-feature_request.yaml b/.github/ISSUE_TEMPLATE/01-feature_request.yaml index 59a95a5cd..d4e4c4c67 100644 --- a/.github/ISSUE_TEMPLATE/01-feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/01-feature_request.yaml @@ -15,7 +15,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v4.6.2 + placeholder: v4.6.3 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/02-bug_report.yaml b/.github/ISSUE_TEMPLATE/02-bug_report.yaml index c222ea3d2..da580c483 100644 --- a/.github/ISSUE_TEMPLATE/02-bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/02-bug_report.yaml @@ -27,7 +27,7 @@ body: attributes: label: NetBox Version description: What version of NetBox are you currently running? - placeholder: v4.6.2 + placeholder: v4.6.3 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/03-performance.yaml b/.github/ISSUE_TEMPLATE/03-performance.yaml index ca6cec3de..8d96543c8 100644 --- a/.github/ISSUE_TEMPLATE/03-performance.yaml +++ b/.github/ISSUE_TEMPLATE/03-performance.yaml @@ -8,7 +8,7 @@ body: attributes: label: NetBox Version description: What version of NetBox are you currently running? - placeholder: v4.6.2 + placeholder: v4.6.3 validations: required: true - type: dropdown diff --git a/contrib/generated_schema.json b/contrib/generated_schema.json index fe881280d..94d3bbb66 100644 --- a/contrib/generated_schema.json +++ b/contrib/generated_schema.json @@ -604,6 +604,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -672,6 +676,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", diff --git a/contrib/openapi.json b/contrib/openapi.json index 1c0a9e17e..e298cf734 100644 --- a/contrib/openapi.json +++ b/contrib/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "NetBox REST API", - "version": "4.6.2", + "version": "4.6.3", "license": { "name": "Apache v2 License" } @@ -12,6 +12,32 @@ "get": { "operationId": "authentication_check_retrieve", "description": "Return the user making the request, if authenticated successfully.", + "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + } + ], "tags": [ "authentication-check" ], @@ -43,6 +69,14 @@ "operationId": "circuits_circuit_group_assignments_list", "description": "Get a list of Circuit group assignment objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "circuit", @@ -167,6 +201,14 @@ "format": "uuid" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -530,6 +572,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -922,7 +972,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitGroupAssignmentRequest" + "$ref": "#/components/schemas/BulkCircuitGroupAssignmentRequest" } } }, @@ -930,7 +980,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitGroupAssignmentRequest" + "$ref": "#/components/schemas/BulkCircuitGroupAssignmentRequest" } } } @@ -973,7 +1023,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitGroupAssignmentRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitGroupAssignmentRequest" } } }, @@ -981,7 +1031,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitGroupAssignmentRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitGroupAssignmentRequest" } } } @@ -1059,6 +1109,22 @@ "operationId": "circuits_circuit_group_assignments_retrieve", "description": "Get a Circuit group assignment object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -1067,6 +1133,14 @@ }, "description": "A unique integer value identifying this Circuit group assignment.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -1235,6 +1309,14 @@ "operationId": "circuits_circuit_groups_list", "description": "Get a list of circuit group objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -1485,6 +1567,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -1838,6 +1928,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -2378,7 +2476,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitGroupRequest" + "$ref": "#/components/schemas/BulkCircuitGroupRequest" } } }, @@ -2386,7 +2484,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitGroupRequest" + "$ref": "#/components/schemas/BulkCircuitGroupRequest" } } } @@ -2429,7 +2527,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitGroupRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitGroupRequest" } } }, @@ -2437,7 +2535,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitGroupRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitGroupRequest" } } } @@ -2515,6 +2613,22 @@ "operationId": "circuits_circuit_groups_retrieve", "description": "Get a circuit group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -2523,6 +2637,14 @@ }, "description": "A unique integer value identifying this circuit group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -2691,6 +2813,14 @@ "operationId": "circuits_circuit_terminations_list", "description": "Get a list of circuit termination objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable_connector", @@ -3244,6 +3374,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -3508,6 +3646,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -4653,7 +4799,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitTerminationRequest" + "$ref": "#/components/schemas/BulkCircuitTerminationRequest" } } }, @@ -4661,7 +4807,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitTerminationRequest" + "$ref": "#/components/schemas/BulkCircuitTerminationRequest" } } } @@ -4704,7 +4850,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitTerminationRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitTerminationRequest" } } }, @@ -4712,7 +4858,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitTerminationRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitTerminationRequest" } } } @@ -4790,6 +4936,22 @@ "operationId": "circuits_circuit_terminations_retrieve", "description": "Get a circuit termination object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -4798,6 +4960,14 @@ }, "description": "A unique integer value identifying this circuit termination.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -4966,6 +5136,22 @@ "operationId": "circuits_circuit_terminations_paths_retrieve", "description": "Return all CablePaths which traverse a given pass-through port.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -4974,6 +5160,14 @@ }, "description": "A unique integer value identifying this circuit termination.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -5006,6 +5200,14 @@ "operationId": "circuits_circuit_types_list", "description": "Get a list of circuit type objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "color", @@ -5407,6 +5609,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -5760,6 +5970,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -6198,7 +6416,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitTypeRequest" + "$ref": "#/components/schemas/BulkCircuitTypeRequest" } } }, @@ -6206,7 +6424,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitTypeRequest" + "$ref": "#/components/schemas/BulkCircuitTypeRequest" } } } @@ -6249,7 +6467,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitTypeRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitTypeRequest" } } }, @@ -6257,7 +6475,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitTypeRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitTypeRequest" } } } @@ -6335,6 +6553,22 @@ "operationId": "circuits_circuit_types_retrieve", "description": "Get a circuit type object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -6343,6 +6577,14 @@ }, "description": "A unique integer value identifying this circuit type.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -6511,6 +6753,14 @@ "operationId": "circuits_circuits_list", "description": "Get a list of circuit objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cid", @@ -7319,6 +7569,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -7632,6 +7890,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -8657,7 +8923,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitRequest" + "$ref": "#/components/schemas/BulkCircuitRequest" } } }, @@ -8665,7 +8931,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitRequest" + "$ref": "#/components/schemas/BulkCircuitRequest" } } } @@ -8708,7 +8974,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitRequest" } } }, @@ -8716,7 +8982,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CircuitRequest" + "$ref": "#/components/schemas/PatchedBulkCircuitRequest" } } } @@ -8794,6 +9060,22 @@ "operationId": "circuits_circuits_retrieve", "description": "Get a circuit object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -8802,6 +9084,14 @@ }, "description": "A unique integer value identifying this circuit.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -9121,6 +9411,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -9447,6 +9745,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -9800,6 +10106,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -10139,7 +10453,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderAccountRequest" + "$ref": "#/components/schemas/BulkProviderAccountRequest" } } }, @@ -10147,7 +10461,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderAccountRequest" + "$ref": "#/components/schemas/BulkProviderAccountRequest" } } } @@ -10190,7 +10504,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderAccountRequest" + "$ref": "#/components/schemas/PatchedBulkProviderAccountRequest" } } }, @@ -10198,7 +10512,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderAccountRequest" + "$ref": "#/components/schemas/PatchedBulkProviderAccountRequest" } } } @@ -10276,6 +10590,22 @@ "operationId": "circuits_provider_accounts_retrieve", "description": "Get a provider account object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -10284,6 +10614,14 @@ }, "description": "A unique integer value identifying this provider account.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -10452,6 +10790,14 @@ "operationId": "circuits_provider_networks_list", "description": "Get a list of provider network objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -10702,6 +11048,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -11055,6 +11409,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -11545,7 +11907,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderNetworkRequest" + "$ref": "#/components/schemas/BulkProviderNetworkRequest" } } }, @@ -11553,7 +11915,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderNetworkRequest" + "$ref": "#/components/schemas/BulkProviderNetworkRequest" } } } @@ -11596,7 +11958,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderNetworkRequest" + "$ref": "#/components/schemas/PatchedBulkProviderNetworkRequest" } } }, @@ -11604,7 +11966,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderNetworkRequest" + "$ref": "#/components/schemas/PatchedBulkProviderNetworkRequest" } } } @@ -11682,6 +12044,22 @@ "operationId": "circuits_provider_networks_retrieve", "description": "Get a provider network object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -11690,6 +12068,14 @@ }, "description": "A unique integer value identifying this provider network.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -11916,6 +12302,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -12242,6 +12636,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -12595,6 +12997,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -13181,7 +13591,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderRequest" + "$ref": "#/components/schemas/BulkProviderRequest" } } }, @@ -13189,7 +13599,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderRequest" + "$ref": "#/components/schemas/BulkProviderRequest" } } } @@ -13232,7 +13642,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderRequest" + "$ref": "#/components/schemas/PatchedBulkProviderRequest" } } }, @@ -13240,7 +13650,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ProviderRequest" + "$ref": "#/components/schemas/PatchedBulkProviderRequest" } } } @@ -13318,6 +13728,22 @@ "operationId": "circuits_providers_retrieve", "description": "Get a provider object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -13326,6 +13752,14 @@ }, "description": "A unique integer value identifying this provider.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -13494,6 +13928,14 @@ "operationId": "circuits_virtual_circuit_terminations_list", "description": "Get a list of virtual circuit termination objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -13744,6 +14186,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -13972,6 +14422,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -14474,7 +14932,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitTerminationRequest" + "$ref": "#/components/schemas/BulkVirtualCircuitTerminationRequest" } } }, @@ -14482,7 +14940,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitTerminationRequest" + "$ref": "#/components/schemas/BulkVirtualCircuitTerminationRequest" } } } @@ -14525,7 +14983,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitTerminationRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualCircuitTerminationRequest" } } }, @@ -14533,7 +14991,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitTerminationRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualCircuitTerminationRequest" } } } @@ -14611,6 +15069,22 @@ "operationId": "circuits_virtual_circuit_terminations_retrieve", "description": "Get a virtual circuit termination object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -14619,6 +15093,14 @@ }, "description": "A unique integer value identifying this virtual circuit termination.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -14787,6 +15269,22 @@ "operationId": "circuits_virtual_circuit_terminations_paths_retrieve", "description": "Return all CablePaths which traverse a given pass-through port.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -14795,6 +15293,14 @@ }, "description": "A unique integer value identifying this virtual circuit termination.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -14827,6 +15333,14 @@ "operationId": "circuits_virtual_circuit_types_list", "description": "Get a list of virtual circuit type objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "color", @@ -15228,6 +15742,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -15581,6 +16103,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -16019,7 +16549,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitTypeRequest" + "$ref": "#/components/schemas/BulkVirtualCircuitTypeRequest" } } }, @@ -16027,7 +16557,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitTypeRequest" + "$ref": "#/components/schemas/BulkVirtualCircuitTypeRequest" } } } @@ -16070,7 +16600,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitTypeRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualCircuitTypeRequest" } } }, @@ -16078,7 +16608,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitTypeRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualCircuitTypeRequest" } } } @@ -16156,6 +16686,22 @@ "operationId": "circuits_virtual_circuit_types_retrieve", "description": "Get a virtual circuit type object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -16164,6 +16710,14 @@ }, "description": "A unique integer value identifying this virtual circuit type.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -16332,6 +16886,14 @@ "operationId": "circuits_virtual_circuits_list", "description": "Get a list of virtual circuit objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cid", @@ -16733,6 +17295,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -16935,6 +17505,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -17671,7 +18249,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitRequest" + "$ref": "#/components/schemas/BulkVirtualCircuitRequest" } } }, @@ -17679,7 +18257,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitRequest" + "$ref": "#/components/schemas/BulkVirtualCircuitRequest" } } } @@ -17722,7 +18300,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualCircuitRequest" } } }, @@ -17730,7 +18308,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualCircuitRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualCircuitRequest" } } } @@ -17808,6 +18386,22 @@ "operationId": "circuits_virtual_circuits_retrieve", "description": "Get a virtual circuit object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -17816,6 +18410,14 @@ }, "description": "A unique integer value identifying this virtual circuit.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -17983,6 +18585,32 @@ "get": { "operationId": "core_background_queues_retrieve", "description": "Retrieve a list of RQ Queues.\nNote: Queue names are not URL safe, so not returning a detail view.", + "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + } + ], "tags": [ "core" ], @@ -18014,6 +18642,22 @@ "operationId": "core_background_queues_retrieve_by_name", "description": "Retrieve a list of RQ Queues.\nNote: Queue names are not URL safe, so not returning a detail view.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "name", @@ -18021,6 +18665,14 @@ "type": "string" }, "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -18053,6 +18705,32 @@ "get": { "operationId": "core_background_tasks_retrieve", "description": "Retrieve a list of RQ Tasks.", + "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + } + ], "tags": [ "core" ], @@ -18084,6 +18762,22 @@ "operationId": "core_background_tasks_retrieve_by_id", "description": "Retrieve a list of RQ Tasks.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -18091,6 +18785,14 @@ "type": "string" }, "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -18339,6 +19041,32 @@ "get": { "operationId": "core_background_workers_retrieve", "description": "Retrieve a list of RQ Workers.", + "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + } + ], "tags": [ "core" ], @@ -18370,6 +19098,22 @@ "operationId": "core_background_workers_retrieve_by_name", "description": "Retrieve a list of RQ Workers.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "name", @@ -18377,6 +19121,14 @@ "type": "string" }, "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -18410,6 +19162,14 @@ "operationId": "core_data_files_list", "description": "Get a list of data file objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -18509,6 +19269,14 @@ "format": "uuid" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "hash", @@ -18862,6 +19630,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -19214,6 +19990,22 @@ "operationId": "core_data_files_retrieve", "description": "Get a data file object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -19222,6 +20014,14 @@ }, "description": "A unique integer value identifying this data file.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -19254,6 +20054,14 @@ "operationId": "core_data_sources_list", "description": "Get a list of data source objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -19511,6 +20319,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -19949,6 +20765,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -20857,7 +21681,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DataSourceRequest" + "$ref": "#/components/schemas/BulkDataSourceRequest" } } }, @@ -20865,7 +21689,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DataSourceRequest" + "$ref": "#/components/schemas/BulkDataSourceRequest" } } } @@ -20908,7 +21732,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DataSourceRequest" + "$ref": "#/components/schemas/PatchedBulkDataSourceRequest" } } }, @@ -20916,7 +21740,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DataSourceRequest" + "$ref": "#/components/schemas/PatchedBulkDataSourceRequest" } } } @@ -20994,6 +21818,22 @@ "operationId": "core_data_sources_retrieve", "description": "Get a data source object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -21002,6 +21842,14 @@ }, "description": "A unique integer value identifying this data source.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -21225,6 +22073,14 @@ "operationId": "core_jobs_list", "description": "Retrieve a list of job results", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "completed", @@ -21273,6 +22129,14 @@ "format": "date-time" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -21918,6 +22782,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -22331,6 +23203,22 @@ "operationId": "core_jobs_retrieve", "description": "Retrieve a list of job results", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -22339,6 +23227,14 @@ }, "description": "A unique integer value identifying this job.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -22528,6 +23424,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "changed_object_id", @@ -22661,6 +23565,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -22915,6 +23827,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -23310,6 +24230,22 @@ "operationId": "core_object_changes_retrieve", "description": "Retrieve a list of recent changes.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -23318,6 +24254,14 @@ }, "description": "A unique integer value identifying this object change.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -23501,6 +24445,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "features", @@ -23508,6 +24460,14 @@ "type": "string" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -23762,6 +24722,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -23826,6 +24794,22 @@ "operationId": "core_object_types_retrieve", "description": "Read-only list of ObjectTypes.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -23834,6 +24818,14 @@ }, "description": "A unique integer value identifying this object type.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -23866,6 +24858,14 @@ "operationId": "dcim_cable_bundles_list", "description": "Get a list of cable bundle objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -24116,6 +25116,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -24469,6 +25477,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -24756,7 +25772,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CableBundleRequest" + "$ref": "#/components/schemas/BulkCableBundleRequest" } } }, @@ -24764,7 +25780,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CableBundleRequest" + "$ref": "#/components/schemas/BulkCableBundleRequest" } } } @@ -24807,7 +25823,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CableBundleRequest" + "$ref": "#/components/schemas/PatchedBulkCableBundleRequest" } } }, @@ -24815,7 +25831,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CableBundleRequest" + "$ref": "#/components/schemas/PatchedBulkCableBundleRequest" } } } @@ -24893,6 +25909,22 @@ "operationId": "dcim_cable_bundles_retrieve", "description": "Get a cable bundle object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -24901,6 +25933,14 @@ }, "description": "A unique integer value identifying this cable bundle.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -25069,6 +26109,14 @@ "operationId": "dcim_cable_terminations_list", "description": "Get a list of cable termination objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable", @@ -25404,6 +26452,14 @@ "format": "uuid" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "frontport_id", @@ -25632,6 +26688,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -25850,6 +26914,22 @@ "operationId": "dcim_cable_terminations_retrieve", "description": "Get a cable termination object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -25858,6 +26938,14 @@ }, "description": "A unique integer value identifying this cable termination.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -25890,6 +26978,14 @@ "operationId": "dcim_cables_list", "description": "Get a list of cable objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "bundle", @@ -26409,6 +27505,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "frontport_id", @@ -27063,6 +28167,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -28136,7 +29248,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CableRequest" + "$ref": "#/components/schemas/BulkCableRequest" } } }, @@ -28144,7 +29256,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CableRequest" + "$ref": "#/components/schemas/BulkCableRequest" } } } @@ -28187,7 +29299,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CableRequest" + "$ref": "#/components/schemas/PatchedBulkCableRequest" } } }, @@ -28195,7 +29307,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CableRequest" + "$ref": "#/components/schemas/PatchedBulkCableRequest" } } } @@ -28273,6 +29385,22 @@ "operationId": "dcim_cables_retrieve", "description": "Get a cable object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -28281,6 +29409,14 @@ }, "description": "A unique integer value identifying this cable.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -28448,6 +29584,30 @@ "operationId": "dcim_connected_device_list", "description": "This endpoint allows a user to determine what device (if any) is connected to a given peer device and peer\ninterface. This is useful in a situation where a device boots with no configuration, but can detect its neighbors\nvia a protocol such as LLDP. Two query parameters must be included in the request:\n\n* `peer_device`: The name of the peer device\n* `peer_interface`: The name of the peer interface", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "in": "query", "name": "peer_device", @@ -28500,6 +29660,14 @@ "operationId": "dcim_console_port_templates_list", "description": "Get a list of console port template objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -28778,6 +29946,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -29310,6 +30486,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -29602,7 +30786,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsolePortTemplateRequest" + "$ref": "#/components/schemas/BulkConsolePortTemplateRequest" } } }, @@ -29610,7 +30794,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsolePortTemplateRequest" + "$ref": "#/components/schemas/BulkConsolePortTemplateRequest" } } } @@ -29653,7 +30837,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsolePortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkConsolePortTemplateRequest" } } }, @@ -29661,7 +30845,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsolePortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkConsolePortTemplateRequest" } } } @@ -29739,6 +30923,22 @@ "operationId": "dcim_console_port_templates_retrieve", "description": "Get a console port template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -29747,6 +30947,14 @@ }, "description": "A unique integer value identifying this console port template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -29915,6 +31123,14 @@ "operationId": "dcim_console_ports_list", "description": "Get a list of console port objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable_connector", @@ -30770,6 +31986,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -31368,6 +32592,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -32318,7 +33550,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsolePortRequest" + "$ref": "#/components/schemas/BulkConsolePortRequest" } } }, @@ -32326,7 +33558,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsolePortRequest" + "$ref": "#/components/schemas/BulkConsolePortRequest" } } } @@ -32369,7 +33601,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsolePortRequest" + "$ref": "#/components/schemas/PatchedBulkConsolePortRequest" } } }, @@ -32377,7 +33609,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsolePortRequest" + "$ref": "#/components/schemas/PatchedBulkConsolePortRequest" } } } @@ -32455,6 +33687,22 @@ "operationId": "dcim_console_ports_retrieve", "description": "Get a console port object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -32463,6 +33711,14 @@ }, "description": "A unique integer value identifying this console port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -32631,6 +33887,22 @@ "operationId": "dcim_console_ports_trace_retrieve", "description": "Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -32639,6 +33911,14 @@ }, "description": "A unique integer value identifying this console port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -32671,6 +33951,14 @@ "operationId": "dcim_console_server_port_templates_list", "description": "Get a list of console server port template objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -32949,6 +34237,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -33481,6 +34777,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -33773,7 +35077,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsoleServerPortTemplateRequest" + "$ref": "#/components/schemas/BulkConsoleServerPortTemplateRequest" } } }, @@ -33781,7 +35085,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsoleServerPortTemplateRequest" + "$ref": "#/components/schemas/BulkConsoleServerPortTemplateRequest" } } } @@ -33824,7 +35128,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsoleServerPortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkConsoleServerPortTemplateRequest" } } }, @@ -33832,7 +35136,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsoleServerPortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkConsoleServerPortTemplateRequest" } } } @@ -33910,6 +35214,22 @@ "operationId": "dcim_console_server_port_templates_retrieve", "description": "Get a console server port template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -33918,6 +35238,14 @@ }, "description": "A unique integer value identifying this console server port template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -34086,6 +35414,14 @@ "operationId": "dcim_console_server_ports_list", "description": "Get a list of console server port objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable_connector", @@ -34941,6 +36277,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -35539,6 +36883,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -36489,7 +37841,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsoleServerPortRequest" + "$ref": "#/components/schemas/BulkConsoleServerPortRequest" } } }, @@ -36497,7 +37849,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsoleServerPortRequest" + "$ref": "#/components/schemas/BulkConsoleServerPortRequest" } } } @@ -36540,7 +37892,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsoleServerPortRequest" + "$ref": "#/components/schemas/PatchedBulkConsoleServerPortRequest" } } }, @@ -36548,7 +37900,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConsoleServerPortRequest" + "$ref": "#/components/schemas/PatchedBulkConsoleServerPortRequest" } } } @@ -36626,6 +37978,22 @@ "operationId": "dcim_console_server_ports_retrieve", "description": "Get a console server port object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -36634,6 +38002,14 @@ }, "description": "A unique integer value identifying this console server port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -36802,6 +38178,22 @@ "operationId": "dcim_console_server_ports_trace_retrieve", "description": "Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -36810,6 +38202,14 @@ }, "description": "A unique integer value identifying this console server port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -36842,6 +38242,14 @@ "operationId": "dcim_device_bay_templates_list", "description": "Get a list of device bay template objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -37125,6 +38533,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -37629,6 +39045,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -37762,7 +39186,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceBayTemplateRequest" + "$ref": "#/components/schemas/BulkDeviceBayTemplateRequest" } } }, @@ -37770,7 +39194,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceBayTemplateRequest" + "$ref": "#/components/schemas/BulkDeviceBayTemplateRequest" } } } @@ -37813,7 +39237,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceBayTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceBayTemplateRequest" } } }, @@ -37821,7 +39245,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceBayTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceBayTemplateRequest" } } } @@ -37899,6 +39323,22 @@ "operationId": "dcim_device_bay_templates_retrieve", "description": "Get a device bay template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -37907,6 +39347,14 @@ }, "description": "A unique integer value identifying this device bay template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -38075,6 +39523,14 @@ "operationId": "dcim_device_bays_list", "description": "Get a list of device bay objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -38653,6 +40109,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -39265,6 +40729,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -39856,7 +41328,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceBayRequest" + "$ref": "#/components/schemas/BulkDeviceBayRequest" } } }, @@ -39864,7 +41336,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceBayRequest" + "$ref": "#/components/schemas/BulkDeviceBayRequest" } } } @@ -39907,7 +41379,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceBayRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceBayRequest" } } }, @@ -39915,7 +41387,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceBayRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceBayRequest" } } } @@ -39993,6 +41465,22 @@ "operationId": "dcim_device_bays_retrieve", "description": "Get a device bay object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -40001,6 +41489,14 @@ }, "description": "A unique integer value identifying this device bay.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -40217,6 +41713,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "color", @@ -40646,6 +42150,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -40999,6 +42511,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -41498,7 +43018,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceRoleRequest" + "$ref": "#/components/schemas/BulkDeviceRoleRequest" } } }, @@ -41506,7 +43026,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceRoleRequest" + "$ref": "#/components/schemas/BulkDeviceRoleRequest" } } } @@ -41549,7 +43069,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceRoleRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceRoleRequest" } } }, @@ -41557,7 +43077,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceRoleRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceRoleRequest" } } } @@ -41635,6 +43155,22 @@ "operationId": "dcim_device_roles_retrieve", "description": "Get a device role object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -41643,6 +43179,14 @@ }, "description": "A unique integer value identifying this device role.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -41984,6 +43528,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "console_port_template_count", @@ -42653,6 +44205,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "front_port_template_count", @@ -43445,6 +45005,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -44803,7 +46371,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceTypeRequest" + "$ref": "#/components/schemas/BulkDeviceTypeRequest" } } }, @@ -44811,7 +46379,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceTypeRequest" + "$ref": "#/components/schemas/BulkDeviceTypeRequest" } } } @@ -44854,7 +46422,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceTypeRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceTypeRequest" } } }, @@ -44862,7 +46430,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceTypeRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceTypeRequest" } } } @@ -44940,6 +46508,22 @@ "operationId": "dcim_device_types_retrieve", "description": "Get a device type object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -44948,6 +46532,14 @@ }, "description": "A unique integer value identifying this device type.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -45440,6 +47032,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cluster_group", @@ -46364,6 +47964,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "front_port_count", @@ -47553,6 +49161,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "in": "query", "name": "oob_ip_id", @@ -49276,7 +50892,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceWithConfigContextRequest" + "$ref": "#/components/schemas/BulkDeviceWithConfigContextRequest" } } }, @@ -49284,7 +50900,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceWithConfigContextRequest" + "$ref": "#/components/schemas/BulkDeviceWithConfigContextRequest" } } } @@ -49327,7 +50943,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceWithConfigContextRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceWithConfigContextRequest" } } }, @@ -49335,7 +50951,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/DeviceWithConfigContextRequest" + "$ref": "#/components/schemas/PatchedBulkDeviceWithConfigContextRequest" } } } @@ -49413,6 +51029,22 @@ "operationId": "dcim_devices_retrieve", "description": "Get a device object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -49421,6 +51053,14 @@ }, "description": "A unique integer value identifying this device.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -49693,6 +51333,14 @@ "operationId": "dcim_front_port_templates_list", "description": "Get a list of front port template objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "color", @@ -50122,6 +51770,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -50654,6 +52310,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -50798,7 +52462,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50818,7 +52482,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50831,7 +52495,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50844,7 +52508,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50857,7 +52521,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50870,7 +52534,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50883,7 +52547,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50896,7 +52560,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50909,7 +52573,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50922,7 +52586,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50935,7 +52599,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -50948,7 +52612,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -51061,7 +52725,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FrontPortTemplateRequest" + "$ref": "#/components/schemas/BulkFrontPortTemplateRequest" } } }, @@ -51069,7 +52733,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FrontPortTemplateRequest" + "$ref": "#/components/schemas/BulkFrontPortTemplateRequest" } } } @@ -51112,7 +52776,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FrontPortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkFrontPortTemplateRequest" } } }, @@ -51120,7 +52784,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FrontPortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkFrontPortTemplateRequest" } } } @@ -51198,6 +52862,22 @@ "operationId": "dcim_front_port_templates_retrieve", "description": "Get a front port template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -51206,6 +52886,14 @@ }, "description": "A unique integer value identifying this front port template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -51374,6 +53062,14 @@ "operationId": "dcim_front_ports_list", "description": "Get a list of front port objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable_connector", @@ -52373,6 +54069,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -52971,6 +54675,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -53521,7 +55233,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53541,7 +55253,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53554,7 +55266,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53567,7 +55279,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53580,7 +55292,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53593,7 +55305,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53606,7 +55318,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53619,7 +55331,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53632,7 +55344,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53645,7 +55357,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53658,7 +55370,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53671,7 +55383,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -53836,7 +55548,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FrontPortRequest" + "$ref": "#/components/schemas/BulkFrontPortRequest" } } }, @@ -53844,7 +55556,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FrontPortRequest" + "$ref": "#/components/schemas/BulkFrontPortRequest" } } } @@ -53887,7 +55599,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FrontPortRequest" + "$ref": "#/components/schemas/PatchedBulkFrontPortRequest" } } }, @@ -53895,7 +55607,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FrontPortRequest" + "$ref": "#/components/schemas/PatchedBulkFrontPortRequest" } } } @@ -53973,6 +55685,22 @@ "operationId": "dcim_front_ports_retrieve", "description": "Get a front port object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -53981,6 +55709,14 @@ }, "description": "A unique integer value identifying this front port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -54149,6 +55885,22 @@ "operationId": "dcim_front_ports_paths_retrieve", "description": "Return all CablePaths which traverse a given pass-through port.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -54157,6 +55909,14 @@ }, "description": "A unique integer value identifying this front port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -54213,6 +55973,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -54498,6 +56266,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -55037,6 +56813,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -55870,7 +57654,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InterfaceTemplateRequest" + "$ref": "#/components/schemas/BulkInterfaceTemplateRequest" } } }, @@ -55878,7 +57662,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InterfaceTemplateRequest" + "$ref": "#/components/schemas/BulkInterfaceTemplateRequest" } } } @@ -55921,7 +57705,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InterfaceTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkInterfaceTemplateRequest" } } }, @@ -55929,7 +57713,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InterfaceTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkInterfaceTemplateRequest" } } } @@ -56007,6 +57791,22 @@ "operationId": "dcim_interface_templates_retrieve", "description": "Get a interface template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -56015,6 +57815,14 @@ }, "description": "A unique integer value identifying this interface template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -56209,6 +58017,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable_connector", @@ -57246,6 +59062,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -58362,6 +60186,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -60772,7 +62604,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InterfaceRequest" + "$ref": "#/components/schemas/BulkInterfaceRequest" } } }, @@ -60780,7 +62612,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InterfaceRequest" + "$ref": "#/components/schemas/BulkInterfaceRequest" } } } @@ -60823,7 +62655,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InterfaceRequest" + "$ref": "#/components/schemas/PatchedBulkInterfaceRequest" } } }, @@ -60831,7 +62663,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InterfaceRequest" + "$ref": "#/components/schemas/PatchedBulkInterfaceRequest" } } } @@ -60909,6 +62741,22 @@ "operationId": "dcim_interfaces_retrieve", "description": "Get a interface object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -60917,6 +62765,14 @@ }, "description": "A unique integer value identifying this interface.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -61085,6 +62941,22 @@ "operationId": "dcim_interfaces_trace_retrieve", "description": "Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -61093,6 +62965,14 @@ }, "description": "A unique integer value identifying this interface.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -61125,6 +63005,14 @@ "operationId": "dcim_inventory_item_roles_list", "description": "Get a list of inventory item role objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "color", @@ -61526,6 +63414,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -61879,6 +63775,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -62317,7 +64221,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemRoleRequest" + "$ref": "#/components/schemas/BulkInventoryItemRoleRequest" } } }, @@ -62325,7 +64229,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemRoleRequest" + "$ref": "#/components/schemas/BulkInventoryItemRoleRequest" } } } @@ -62368,7 +64272,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemRoleRequest" + "$ref": "#/components/schemas/PatchedBulkInventoryItemRoleRequest" } } }, @@ -62376,7 +64280,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemRoleRequest" + "$ref": "#/components/schemas/PatchedBulkInventoryItemRoleRequest" } } } @@ -62454,6 +64358,22 @@ "operationId": "dcim_inventory_item_roles_retrieve", "description": "Get a inventory item role object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -62462,6 +64382,14 @@ }, "description": "A unique integer value identifying this inventory item role.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -62630,6 +64558,14 @@ "operationId": "dcim_inventory_item_templates_list", "description": "Get a list of inventory item template objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "component_id", @@ -63021,6 +64957,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -63579,6 +65523,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -63945,7 +65897,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemTemplateRequest" + "$ref": "#/components/schemas/BulkInventoryItemTemplateRequest" } } }, @@ -63953,7 +65905,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemTemplateRequest" + "$ref": "#/components/schemas/BulkInventoryItemTemplateRequest" } } } @@ -63996,7 +65948,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkInventoryItemTemplateRequest" } } }, @@ -64004,7 +65956,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkInventoryItemTemplateRequest" } } } @@ -64082,6 +66034,22 @@ "operationId": "dcim_inventory_item_templates_retrieve", "description": "Get a inventory item template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -64090,6 +66058,14 @@ }, "description": "A unique integer value identifying this inventory item template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -64409,6 +66385,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "component_id", @@ -65102,6 +67086,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -65712,6 +67704,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -66850,7 +68850,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemRequest" + "$ref": "#/components/schemas/BulkInventoryItemRequest" } } }, @@ -66858,7 +68858,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemRequest" + "$ref": "#/components/schemas/BulkInventoryItemRequest" } } } @@ -66901,7 +68901,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemRequest" + "$ref": "#/components/schemas/PatchedBulkInventoryItemRequest" } } }, @@ -66909,7 +68909,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/InventoryItemRequest" + "$ref": "#/components/schemas/PatchedBulkInventoryItemRequest" } } } @@ -66987,6 +68987,22 @@ "operationId": "dcim_inventory_items_retrieve", "description": "Get a inventory item object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -66995,6 +69011,14 @@ }, "description": "A unique integer value identifying this inventory item.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -67211,6 +69235,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -67688,6 +69720,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -68041,6 +70081,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -68946,7 +70994,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/LocationRequest" + "$ref": "#/components/schemas/BulkLocationRequest" } } }, @@ -68954,7 +71002,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/LocationRequest" + "$ref": "#/components/schemas/BulkLocationRequest" } } } @@ -68997,7 +71045,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/LocationRequest" + "$ref": "#/components/schemas/PatchedBulkLocationRequest" } } }, @@ -69005,7 +71053,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/LocationRequest" + "$ref": "#/components/schemas/PatchedBulkLocationRequest" } } } @@ -69083,6 +71131,22 @@ "operationId": "dcim_locations_retrieve", "description": "Get a location object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -69091,6 +71155,14 @@ }, "description": "A unique integer value identifying this location.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -69376,6 +71448,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -69651,6 +71731,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -70049,6 +72137,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -70421,7 +72517,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/MACAddressRequest" + "$ref": "#/components/schemas/BulkMACAddressRequest" } } }, @@ -70429,7 +72525,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/MACAddressRequest" + "$ref": "#/components/schemas/BulkMACAddressRequest" } } } @@ -70472,7 +72568,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/MACAddressRequest" + "$ref": "#/components/schemas/PatchedBulkMACAddressRequest" } } }, @@ -70480,7 +72576,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/MACAddressRequest" + "$ref": "#/components/schemas/PatchedBulkMACAddressRequest" } } } @@ -70558,6 +72654,22 @@ "operationId": "dcim_mac_addresses_retrieve", "description": "Get a MAC address object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -70566,6 +72678,14 @@ }, "description": "A unique integer value identifying this MAC address.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -70734,6 +72854,14 @@ "operationId": "dcim_manufacturers_list", "description": "Get a list of manufacturer objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -71060,6 +73188,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -71413,6 +73549,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -71851,7 +73995,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ManufacturerRequest" + "$ref": "#/components/schemas/BulkManufacturerRequest" } } }, @@ -71859,7 +74003,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ManufacturerRequest" + "$ref": "#/components/schemas/BulkManufacturerRequest" } } } @@ -71902,7 +74046,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ManufacturerRequest" + "$ref": "#/components/schemas/PatchedBulkManufacturerRequest" } } }, @@ -71910,7 +74054,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ManufacturerRequest" + "$ref": "#/components/schemas/PatchedBulkManufacturerRequest" } } } @@ -71988,6 +74132,22 @@ "operationId": "dcim_manufacturers_retrieve", "description": "Get a manufacturer object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -71996,6 +74156,14 @@ }, "description": "A unique integer value identifying this manufacturer.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -72164,6 +74332,14 @@ "operationId": "dcim_module_bay_templates_list", "description": "Get a list of module bay template objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -72449,6 +74625,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -72981,6 +75165,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -73265,7 +75457,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleBayTemplateRequest" + "$ref": "#/components/schemas/BulkModuleBayTemplateRequest" } } }, @@ -73273,7 +75465,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleBayTemplateRequest" + "$ref": "#/components/schemas/BulkModuleBayTemplateRequest" } } } @@ -73316,7 +75508,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleBayTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkModuleBayTemplateRequest" } } }, @@ -73324,7 +75516,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleBayTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkModuleBayTemplateRequest" } } } @@ -73402,6 +75594,22 @@ "operationId": "dcim_module_bay_templates_retrieve", "description": "Get a module bay template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -73410,6 +75618,14 @@ }, "description": "A unique integer value identifying this module bay template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -73578,6 +75794,14 @@ "operationId": "dcim_module_bays_list", "description": "Get a list of module bay objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -74156,6 +76380,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -74766,6 +76998,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -75536,7 +77776,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleBayRequest" + "$ref": "#/components/schemas/BulkModuleBayRequest" } } }, @@ -75544,7 +77784,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleBayRequest" + "$ref": "#/components/schemas/BulkModuleBayRequest" } } } @@ -75587,7 +77827,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleBayRequest" + "$ref": "#/components/schemas/PatchedBulkModuleBayRequest" } } }, @@ -75595,7 +77835,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleBayRequest" + "$ref": "#/components/schemas/PatchedBulkModuleBayRequest" } } } @@ -75673,6 +77913,22 @@ "operationId": "dcim_module_bays_retrieve", "description": "Get a module bay object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -75681,6 +77937,14 @@ }, "description": "A unique integer value identifying this module bay.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -75849,6 +78113,14 @@ "operationId": "dcim_module_type_profiles_list", "description": "Get a list of module type profile objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -76099,6 +78371,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -76452,6 +78732,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -76739,7 +79027,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleTypeProfileRequest" + "$ref": "#/components/schemas/BulkModuleTypeProfileRequest" } } }, @@ -76747,7 +79035,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleTypeProfileRequest" + "$ref": "#/components/schemas/BulkModuleTypeProfileRequest" } } } @@ -76790,7 +79078,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleTypeProfileRequest" + "$ref": "#/components/schemas/PatchedBulkModuleTypeProfileRequest" } } }, @@ -76798,7 +79086,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleTypeProfileRequest" + "$ref": "#/components/schemas/PatchedBulkModuleTypeProfileRequest" } } } @@ -76876,6 +79164,22 @@ "operationId": "dcim_module_type_profiles_retrieve", "description": "Get a module type profile object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -76884,6 +79188,14 @@ }, "description": "A unique integer value identifying this module type profile.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -77217,6 +79529,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "console_port_template_count", @@ -77653,6 +79973,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "front_port_template_count", @@ -78414,6 +80742,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -79431,7 +81767,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleTypeRequest" + "$ref": "#/components/schemas/BulkModuleTypeRequest" } } }, @@ -79439,7 +81775,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleTypeRequest" + "$ref": "#/components/schemas/BulkModuleTypeRequest" } } } @@ -79482,7 +81818,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleTypeRequest" + "$ref": "#/components/schemas/PatchedBulkModuleTypeRequest" } } }, @@ -79490,7 +81826,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleTypeRequest" + "$ref": "#/components/schemas/PatchedBulkModuleTypeRequest" } } } @@ -79568,6 +81904,22 @@ "operationId": "dcim_module_types_retrieve", "description": "Get a module type object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -79576,6 +81928,14 @@ }, "description": "A unique integer value identifying this module type.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -79895,6 +82255,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -80199,6 +82567,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -80581,6 +82957,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -81434,7 +83818,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleRequest" + "$ref": "#/components/schemas/BulkModuleRequest" } } }, @@ -81442,7 +83826,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleRequest" + "$ref": "#/components/schemas/BulkModuleRequest" } } } @@ -81485,7 +83869,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleRequest" + "$ref": "#/components/schemas/PatchedBulkModuleRequest" } } }, @@ -81493,7 +83877,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ModuleRequest" + "$ref": "#/components/schemas/PatchedBulkModuleRequest" } } } @@ -81571,6 +83955,22 @@ "operationId": "dcim_modules_retrieve", "description": "Get a module object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -81579,6 +83979,14 @@ }, "description": "A unique integer value identifying this module.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -81802,6 +84210,14 @@ "type": "string" } }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "config_template_id", @@ -82080,6 +84496,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -82485,6 +84909,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -82977,7 +85409,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PlatformRequest" + "$ref": "#/components/schemas/BulkPlatformRequest" } } }, @@ -82985,7 +85417,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PlatformRequest" + "$ref": "#/components/schemas/BulkPlatformRequest" } } } @@ -83028,7 +85460,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PlatformRequest" + "$ref": "#/components/schemas/PatchedBulkPlatformRequest" } } }, @@ -83036,7 +85468,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PlatformRequest" + "$ref": "#/components/schemas/PatchedBulkPlatformRequest" } } } @@ -83114,6 +85546,22 @@ "operationId": "dcim_platforms_retrieve", "description": "Get a platform object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -83122,6 +85570,14 @@ }, "description": "A unique integer value identifying this platform.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -83460,6 +85916,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable_connector", @@ -83994,6 +86458,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -84446,6 +86918,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -85748,7 +88228,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerFeedRequest" + "$ref": "#/components/schemas/BulkPowerFeedRequest" } } }, @@ -85756,7 +88236,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerFeedRequest" + "$ref": "#/components/schemas/BulkPowerFeedRequest" } } } @@ -85799,7 +88279,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerFeedRequest" + "$ref": "#/components/schemas/PatchedBulkPowerFeedRequest" } } }, @@ -85807,7 +88287,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerFeedRequest" + "$ref": "#/components/schemas/PatchedBulkPowerFeedRequest" } } } @@ -85885,6 +88365,22 @@ "operationId": "dcim_power_feeds_retrieve", "description": "Get a power feed object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -85893,6 +88389,14 @@ }, "description": "A unique integer value identifying this power feed.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -86061,6 +88565,22 @@ "operationId": "dcim_power_feeds_trace_retrieve", "description": "Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -86069,6 +88589,14 @@ }, "description": "A unique integer value identifying this power feed.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -86101,6 +88629,14 @@ "operationId": "dcim_power_outlet_templates_list", "description": "Get a list of power outlet template objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "color", @@ -86717,6 +89253,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -87249,6 +89793,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -87587,7 +90139,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerOutletTemplateRequest" + "$ref": "#/components/schemas/BulkPowerOutletTemplateRequest" } } }, @@ -87595,7 +90147,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerOutletTemplateRequest" + "$ref": "#/components/schemas/BulkPowerOutletTemplateRequest" } } } @@ -87638,7 +90190,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerOutletTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkPowerOutletTemplateRequest" } } }, @@ -87646,7 +90198,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerOutletTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkPowerOutletTemplateRequest" } } } @@ -87724,6 +90276,22 @@ "operationId": "dcim_power_outlet_templates_retrieve", "description": "Get a power outlet template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -87732,6 +90300,14 @@ }, "description": "A unique integer value identifying this power outlet template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -87900,6 +90476,14 @@ "operationId": "dcim_power_outlets_list", "description": "Get a list of power outlet objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable_connector", @@ -89093,6 +91677,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -89691,6 +92283,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -90660,7 +93260,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerOutletRequest" + "$ref": "#/components/schemas/BulkPowerOutletRequest" } } }, @@ -90668,7 +93268,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerOutletRequest" + "$ref": "#/components/schemas/BulkPowerOutletRequest" } } } @@ -90711,7 +93311,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerOutletRequest" + "$ref": "#/components/schemas/PatchedBulkPowerOutletRequest" } } }, @@ -90719,7 +93319,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerOutletRequest" + "$ref": "#/components/schemas/PatchedBulkPowerOutletRequest" } } } @@ -90797,6 +93397,22 @@ "operationId": "dcim_power_outlets_retrieve", "description": "Get a power outlet object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -90805,6 +93421,14 @@ }, "description": "A unique integer value identifying this power outlet.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -90973,6 +93597,22 @@ "operationId": "dcim_power_outlets_trace_retrieve", "description": "Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -90981,6 +93621,14 @@ }, "description": "A unique integer value identifying this power outlet.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -91013,6 +93661,14 @@ "operationId": "dcim_power_panels_list", "description": "Get a list of power panel objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -91339,6 +93995,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -91716,6 +94380,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -92151,7 +94823,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPanelRequest" + "$ref": "#/components/schemas/BulkPowerPanelRequest" } } }, @@ -92159,7 +94831,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPanelRequest" + "$ref": "#/components/schemas/BulkPowerPanelRequest" } } } @@ -92202,7 +94874,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPanelRequest" + "$ref": "#/components/schemas/PatchedBulkPowerPanelRequest" } } }, @@ -92210,7 +94882,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPanelRequest" + "$ref": "#/components/schemas/PatchedBulkPowerPanelRequest" } } } @@ -92288,6 +94960,22 @@ "operationId": "dcim_power_panels_retrieve", "description": "Get a power panel object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -92296,6 +94984,14 @@ }, "description": "A unique integer value identifying this power panel.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -92549,6 +95245,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -92827,6 +95531,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -93444,6 +96156,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -93754,7 +96474,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPortTemplateRequest" + "$ref": "#/components/schemas/BulkPowerPortTemplateRequest" } } }, @@ -93762,7 +96482,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPortTemplateRequest" + "$ref": "#/components/schemas/BulkPowerPortTemplateRequest" } } } @@ -93805,7 +96525,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkPowerPortTemplateRequest" } } }, @@ -93813,7 +96533,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkPowerPortTemplateRequest" } } } @@ -93891,6 +96611,22 @@ "operationId": "dcim_power_port_templates_retrieve", "description": "Get a power port template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -93899,6 +96635,14 @@ }, "description": "A unique integer value identifying this power port template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -94152,6 +96896,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable_connector", @@ -95007,6 +97759,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -95690,6 +98450,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -96468,7 +99236,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPortRequest" + "$ref": "#/components/schemas/BulkPowerPortRequest" } } }, @@ -96476,7 +99244,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPortRequest" + "$ref": "#/components/schemas/BulkPowerPortRequest" } } } @@ -96519,7 +99287,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPortRequest" + "$ref": "#/components/schemas/PatchedBulkPowerPortRequest" } } }, @@ -96527,7 +99295,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PowerPortRequest" + "$ref": "#/components/schemas/PatchedBulkPowerPortRequest" } } } @@ -96605,6 +99373,22 @@ "operationId": "dcim_power_ports_retrieve", "description": "Get a power port object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -96613,6 +99397,14 @@ }, "description": "A unique integer value identifying this power port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -96781,6 +99573,22 @@ "operationId": "dcim_power_ports_trace_retrieve", "description": "Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -96789,6 +99597,14 @@ }, "description": "A unique integer value identifying this power port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -96821,6 +99637,14 @@ "operationId": "dcim_rack_groups_list", "description": "Get a list of rack group objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -97071,6 +99895,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -97424,6 +100256,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -97862,7 +100702,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackGroupRequest" + "$ref": "#/components/schemas/BulkRackGroupRequest" } } }, @@ -97870,7 +100710,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackGroupRequest" + "$ref": "#/components/schemas/BulkRackGroupRequest" } } } @@ -97913,7 +100753,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackGroupRequest" + "$ref": "#/components/schemas/PatchedBulkRackGroupRequest" } } }, @@ -97921,7 +100761,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackGroupRequest" + "$ref": "#/components/schemas/PatchedBulkRackGroupRequest" } } } @@ -97999,6 +100839,22 @@ "operationId": "dcim_rack_groups_retrieve", "description": "Get a rack group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -98007,6 +100863,14 @@ }, "description": "A unique integer value identifying this rack group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -98175,6 +101039,14 @@ "operationId": "dcim_rack_reservations_list", "description": "Get a list of rack reservation objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -98425,6 +101297,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -98727,6 +101607,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -99570,7 +102458,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackReservationRequest" + "$ref": "#/components/schemas/BulkRackReservationRequest" } } }, @@ -99578,7 +102466,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackReservationRequest" + "$ref": "#/components/schemas/BulkRackReservationRequest" } } } @@ -99621,7 +102509,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackReservationRequest" + "$ref": "#/components/schemas/PatchedBulkRackReservationRequest" } } }, @@ -99629,7 +102517,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackReservationRequest" + "$ref": "#/components/schemas/PatchedBulkRackReservationRequest" } } } @@ -99707,6 +102595,22 @@ "operationId": "dcim_rack_reservations_retrieve", "description": "Get a rack reservation object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -99715,6 +102619,14 @@ }, "description": "A unique integer value identifying this rack reservation.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -99883,6 +102795,14 @@ "operationId": "dcim_rack_roles_list", "description": "Get a list of rack role objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "color", @@ -100284,6 +103204,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -100637,6 +103565,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -101075,7 +104011,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackRoleRequest" + "$ref": "#/components/schemas/BulkRackRoleRequest" } } }, @@ -101083,7 +104019,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackRoleRequest" + "$ref": "#/components/schemas/BulkRackRoleRequest" } } } @@ -101126,7 +104062,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackRoleRequest" + "$ref": "#/components/schemas/PatchedBulkRackRoleRequest" } } }, @@ -101134,7 +104070,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackRoleRequest" + "$ref": "#/components/schemas/PatchedBulkRackRoleRequest" } } } @@ -101212,6 +104148,22 @@ "operationId": "dcim_rack_roles_retrieve", "description": "Get a rack role object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -101220,6 +104172,14 @@ }, "description": "A unique integer value identifying this rack role.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -101388,6 +104348,14 @@ "operationId": "dcim_rack_types_list", "description": "Get a list of rack type objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -101645,6 +104613,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "form_factor", @@ -102383,6 +105359,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -103902,7 +106886,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackTypeRequest" + "$ref": "#/components/schemas/BulkRackTypeRequest" } } }, @@ -103910,7 +106894,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackTypeRequest" + "$ref": "#/components/schemas/BulkRackTypeRequest" } } } @@ -103953,7 +106937,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackTypeRequest" + "$ref": "#/components/schemas/PatchedBulkRackTypeRequest" } } }, @@ -103961,7 +106945,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackTypeRequest" + "$ref": "#/components/schemas/PatchedBulkRackTypeRequest" } } } @@ -104039,6 +107023,22 @@ "operationId": "dcim_rack_types_retrieve", "description": "Get a rack type object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -104047,6 +107047,14 @@ }, "description": "A unique integer value identifying this rack type.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -104523,6 +107531,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -105007,6 +108023,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "form_factor", @@ -105859,6 +108883,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -107814,7 +110846,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackRequest" + "$ref": "#/components/schemas/BulkRackRequest" } } }, @@ -107822,7 +110854,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackRequest" + "$ref": "#/components/schemas/BulkRackRequest" } } } @@ -107865,7 +110897,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackRequest" + "$ref": "#/components/schemas/PatchedBulkRackRequest" } } }, @@ -107873,7 +110905,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RackRequest" + "$ref": "#/components/schemas/PatchedBulkRackRequest" } } } @@ -107951,6 +110983,22 @@ "operationId": "dcim_racks_retrieve", "description": "Get a rack object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -107959,6 +111007,14 @@ }, "description": "A unique integer value identifying this rack.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -108127,6 +111183,14 @@ "operationId": "dcim_racks_elevation_retrieve", "description": "Rack elevation representing the list of rack units. Also supports rendering the elevation as an SVG.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "exclude", @@ -108157,6 +111221,14 @@ }, "description": "* `front` - Front\n* `rear` - Rear" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -108208,6 +111280,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "in": "query", "name": "q", @@ -108285,6 +111365,14 @@ "operationId": "dcim_rear_port_templates_list", "description": "Get a list of rear port template objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "color", @@ -108714,6 +111802,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "front_port_id", @@ -109272,6 +112368,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -109390,7 +112494,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109410,7 +112514,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109423,7 +112527,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109436,7 +112540,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109449,7 +112553,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109462,7 +112566,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109475,7 +112579,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109488,7 +112592,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109501,7 +112605,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109514,7 +112618,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109527,7 +112631,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109540,7 +112644,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -109653,7 +112757,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RearPortTemplateRequest" + "$ref": "#/components/schemas/BulkRearPortTemplateRequest" } } }, @@ -109661,7 +112765,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RearPortTemplateRequest" + "$ref": "#/components/schemas/BulkRearPortTemplateRequest" } } } @@ -109704,7 +112808,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RearPortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkRearPortTemplateRequest" } } }, @@ -109712,7 +112816,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RearPortTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkRearPortTemplateRequest" } } } @@ -109790,6 +112894,22 @@ "operationId": "dcim_rear_port_templates_retrieve", "description": "Get a rear port template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -109798,6 +112918,14 @@ }, "description": "A unique integer value identifying this rear port template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -109966,6 +113094,14 @@ "operationId": "dcim_rear_ports_list", "description": "Get a list of rear port objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cable_connector", @@ -110965,6 +114101,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "front_port_id", @@ -111589,6 +114733,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -112113,7 +115265,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112133,7 +115285,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112146,7 +115298,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112159,7 +115311,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112172,7 +115324,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112185,7 +115337,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112198,7 +115350,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112211,7 +115363,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112224,7 +115376,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112237,7 +115389,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112250,7 +115402,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112263,7 +115415,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "2696b7065f33307c" + "x-spec-enum-id": "b64d6804afec405c" } }, "explode": true, @@ -112428,7 +115580,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RearPortRequest" + "$ref": "#/components/schemas/BulkRearPortRequest" } } }, @@ -112436,7 +115588,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RearPortRequest" + "$ref": "#/components/schemas/BulkRearPortRequest" } } } @@ -112479,7 +115631,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RearPortRequest" + "$ref": "#/components/schemas/PatchedBulkRearPortRequest" } } }, @@ -112487,7 +115639,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RearPortRequest" + "$ref": "#/components/schemas/PatchedBulkRearPortRequest" } } } @@ -112565,6 +115717,22 @@ "operationId": "dcim_rear_ports_retrieve", "description": "Get a rear port object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -112573,6 +115741,14 @@ }, "description": "A unique integer value identifying this rear port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -112741,6 +115917,22 @@ "operationId": "dcim_rear_ports_paths_retrieve", "description": "Return all CablePaths which traverse a given pass-through port.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -112749,6 +115941,14 @@ }, "description": "A unique integer value identifying this rear port.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -112829,6 +116029,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -113155,6 +116363,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -113508,6 +116724,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -114000,7 +117224,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RegionRequest" + "$ref": "#/components/schemas/BulkRegionRequest" } } }, @@ -114008,7 +117232,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RegionRequest" + "$ref": "#/components/schemas/BulkRegionRequest" } } } @@ -114051,7 +117275,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RegionRequest" + "$ref": "#/components/schemas/PatchedBulkRegionRequest" } } }, @@ -114059,7 +117283,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RegionRequest" + "$ref": "#/components/schemas/PatchedBulkRegionRequest" } } } @@ -114137,6 +117361,22 @@ "operationId": "dcim_regions_retrieve", "description": "Get a region object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -114145,6 +117385,14 @@ }, "description": "A unique integer value identifying this region.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -114361,6 +117609,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -114687,6 +117943,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -115040,6 +118304,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -115532,7 +118804,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SiteGroupRequest" + "$ref": "#/components/schemas/BulkSiteGroupRequest" } } }, @@ -115540,7 +118812,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SiteGroupRequest" + "$ref": "#/components/schemas/BulkSiteGroupRequest" } } } @@ -115583,7 +118855,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SiteGroupRequest" + "$ref": "#/components/schemas/PatchedBulkSiteGroupRequest" } } }, @@ -115591,7 +118863,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SiteGroupRequest" + "$ref": "#/components/schemas/PatchedBulkSiteGroupRequest" } } } @@ -115669,6 +118941,22 @@ "operationId": "dcim_site_groups_retrieve", "description": "Get a site group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -115677,6 +118965,14 @@ }, "description": "A unique integer value identifying this site group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -115903,6 +119199,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -116380,6 +119684,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -116951,6 +120263,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -117846,7 +121166,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SiteRequest" + "$ref": "#/components/schemas/BulkSiteRequest" } } }, @@ -117854,7 +121174,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SiteRequest" + "$ref": "#/components/schemas/BulkSiteRequest" } } } @@ -117897,7 +121217,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SiteRequest" + "$ref": "#/components/schemas/PatchedBulkSiteRequest" } } }, @@ -117905,7 +121225,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SiteRequest" + "$ref": "#/components/schemas/PatchedBulkSiteRequest" } } } @@ -117983,6 +121303,22 @@ "operationId": "dcim_sites_retrieve", "description": "Get a site object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -117991,6 +121327,14 @@ }, "description": "A unique integer value identifying this site.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -118159,6 +121503,14 @@ "operationId": "dcim_virtual_chassis_list", "description": "Get a list of virtual chassis objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -118560,6 +121912,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -119054,6 +122414,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -119541,7 +122909,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualChassisRequest" + "$ref": "#/components/schemas/BulkVirtualChassisRequest" } } }, @@ -119549,7 +122917,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualChassisRequest" + "$ref": "#/components/schemas/BulkVirtualChassisRequest" } } } @@ -119592,7 +122960,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualChassisRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualChassisRequest" } } }, @@ -119600,7 +122968,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualChassisRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualChassisRequest" } } } @@ -119678,6 +123046,22 @@ "operationId": "dcim_virtual_chassis_retrieve", "description": "Get a virtual chassis object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -119686,6 +123070,14 @@ }, "description": "A unique integer value identifying this virtual chassis.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -119854,6 +123246,14 @@ "operationId": "dcim_virtual_device_contexts_list", "description": "Get a list of virtual device context objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -120156,6 +123556,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "has_primary_ip", @@ -120628,6 +124036,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -121284,7 +124700,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualDeviceContextRequest" + "$ref": "#/components/schemas/BulkVirtualDeviceContextRequest" } } }, @@ -121292,7 +124708,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualDeviceContextRequest" + "$ref": "#/components/schemas/BulkVirtualDeviceContextRequest" } } } @@ -121335,7 +124751,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualDeviceContextRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualDeviceContextRequest" } } }, @@ -121343,7 +124759,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualDeviceContextRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualDeviceContextRequest" } } } @@ -121421,6 +124837,22 @@ "operationId": "dcim_virtual_device_contexts_retrieve", "description": "Get a virtual device context object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -121429,6 +124861,14 @@ }, "description": "A unique integer value identifying this virtual device context.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -121597,6 +125037,14 @@ "operationId": "extras_bookmarks_list", "description": "Get a list of bookmark objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -121605,6 +125053,14 @@ "format": "date-time" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -121908,6 +125364,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -122077,7 +125541,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/BookmarkRequest" + "$ref": "#/components/schemas/BulkBookmarkRequest" } } }, @@ -122085,7 +125549,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/BookmarkRequest" + "$ref": "#/components/schemas/BulkBookmarkRequest" } } } @@ -122128,7 +125592,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/BookmarkRequest" + "$ref": "#/components/schemas/PatchedBulkBookmarkRequest" } } }, @@ -122136,7 +125600,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/BookmarkRequest" + "$ref": "#/components/schemas/PatchedBulkBookmarkRequest" } } } @@ -122214,6 +125678,22 @@ "operationId": "extras_bookmarks_retrieve", "description": "Get a bookmark object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -122222,6 +125702,14 @@ }, "description": "A unique integer value identifying this bookmark.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -122397,6 +125885,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -122788,6 +126284,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -123141,6 +126645,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -123428,7 +126940,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigContextProfileRequest" + "$ref": "#/components/schemas/BulkConfigContextProfileRequest" } } }, @@ -123436,7 +126948,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigContextProfileRequest" + "$ref": "#/components/schemas/BulkConfigContextProfileRequest" } } } @@ -123479,7 +126991,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigContextProfileRequest" + "$ref": "#/components/schemas/PatchedBulkConfigContextProfileRequest" } } }, @@ -123487,7 +126999,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigContextProfileRequest" + "$ref": "#/components/schemas/PatchedBulkConfigContextProfileRequest" } } } @@ -123565,6 +127077,22 @@ "operationId": "extras_config_context_profiles_retrieve", "description": "Get a config context profile object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -123573,6 +127101,14 @@ }, "description": "A unique integer value identifying this config context profile.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -123803,6 +127339,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cluster_group", @@ -124402,6 +127946,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -124814,6 +128366,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -125556,7 +129116,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigContextRequest" + "$ref": "#/components/schemas/BulkConfigContextRequest" } } }, @@ -125564,7 +129124,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigContextRequest" + "$ref": "#/components/schemas/BulkConfigContextRequest" } } } @@ -125607,7 +129167,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigContextRequest" + "$ref": "#/components/schemas/PatchedBulkConfigContextRequest" } } }, @@ -125615,7 +129175,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigContextRequest" + "$ref": "#/components/schemas/PatchedBulkConfigContextRequest" } } } @@ -125693,6 +129253,22 @@ "operationId": "extras_config_contexts_retrieve", "description": "Get a config context object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -125701,6 +129277,14 @@ }, "description": "A unique integer value identifying this config context.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -125938,6 +129522,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -126336,6 +129928,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "file_extension", @@ -127142,6 +130742,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -127429,7 +131037,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigTemplateRequest" + "$ref": "#/components/schemas/BulkConfigTemplateRequest" } } }, @@ -127437,7 +131045,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigTemplateRequest" + "$ref": "#/components/schemas/BulkConfigTemplateRequest" } } } @@ -127480,7 +131088,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkConfigTemplateRequest" } } }, @@ -127488,7 +131096,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ConfigTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkConfigTemplateRequest" } } } @@ -127566,6 +131174,22 @@ "operationId": "extras_config_templates_retrieve", "description": "Get a config template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -127574,6 +131198,14 @@ }, "description": "A unique integer value identifying this config template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -128045,6 +131677,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "choice", @@ -128318,6 +131958,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -128671,6 +132319,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "in": "query", "name": "order_alphabetically", @@ -128917,7 +132573,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomFieldChoiceSetRequest" + "$ref": "#/components/schemas/BulkCustomFieldChoiceSetRequest" } } }, @@ -128925,7 +132581,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomFieldChoiceSetRequest" + "$ref": "#/components/schemas/BulkCustomFieldChoiceSetRequest" } } } @@ -128968,7 +132624,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomFieldChoiceSetRequest" + "$ref": "#/components/schemas/PatchedBulkCustomFieldChoiceSetRequest" } } }, @@ -128976,7 +132632,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomFieldChoiceSetRequest" + "$ref": "#/components/schemas/PatchedBulkCustomFieldChoiceSetRequest" } } } @@ -129054,6 +132710,22 @@ "operationId": "extras_custom_field_choice_sets_retrieve", "description": "Get a custom field choice set object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -129062,6 +132734,14 @@ }, "description": "A unique integer value identifying this custom field choice set.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -129230,6 +132910,22 @@ "operationId": "extras_custom_field_choice_sets_choices_retrieve", "description": "Provides an endpoint to iterate through each choice in a set.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -129238,6 +132934,14 @@ }, "description": "A unique integer value identifying this custom field choice set.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -129270,6 +132974,14 @@ "operationId": "extras_custom_fields_list", "description": "Get a list of custom field objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "choice_set", @@ -129570,6 +133282,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "filter_logic", @@ -130557,6 +134277,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -131838,7 +135566,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomFieldRequest" + "$ref": "#/components/schemas/BulkCustomFieldRequest" } } }, @@ -131846,7 +135574,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomFieldRequest" + "$ref": "#/components/schemas/BulkCustomFieldRequest" } } } @@ -131889,7 +135617,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomFieldRequest" + "$ref": "#/components/schemas/PatchedBulkCustomFieldRequest" } } }, @@ -131897,7 +135625,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomFieldRequest" + "$ref": "#/components/schemas/PatchedBulkCustomFieldRequest" } } } @@ -131975,6 +135703,22 @@ "operationId": "extras_custom_fields_retrieve", "description": "Get a custom field object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -131983,6 +135727,14 @@ }, "description": "A unique integer value identifying this custom field.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -132151,6 +135903,14 @@ "operationId": "extras_custom_links_list", "description": "Get a list of custom link objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "button_class", @@ -132438,6 +136198,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group_name", @@ -133285,6 +137053,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -133609,7 +137385,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomLinkRequest" + "$ref": "#/components/schemas/BulkCustomLinkRequest" } } }, @@ -133617,7 +137393,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomLinkRequest" + "$ref": "#/components/schemas/BulkCustomLinkRequest" } } } @@ -133660,7 +137436,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomLinkRequest" + "$ref": "#/components/schemas/PatchedBulkCustomLinkRequest" } } }, @@ -133668,7 +137444,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomLinkRequest" + "$ref": "#/components/schemas/PatchedBulkCustomLinkRequest" } } } @@ -133746,6 +137522,22 @@ "operationId": "extras_custom_links_retrieve", "description": "Get a custom link object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -133754,6 +137546,14 @@ }, "description": "A unique integer value identifying this custom link.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -133921,6 +137721,32 @@ "get": { "operationId": "extras_dashboard_retrieve", "description": "Get a list of dashboard objects.", + "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + } + ], "tags": [ "extras" ], @@ -134331,6 +138157,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -134600,6 +138434,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -135121,6 +138963,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -135408,7 +139258,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/EventRuleRequest" + "$ref": "#/components/schemas/BulkEventRuleRequest" } } }, @@ -135416,7 +139266,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/EventRuleRequest" + "$ref": "#/components/schemas/BulkEventRuleRequest" } } } @@ -135459,7 +139309,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/EventRuleRequest" + "$ref": "#/components/schemas/PatchedBulkEventRuleRequest" } } }, @@ -135467,7 +139317,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/EventRuleRequest" + "$ref": "#/components/schemas/PatchedBulkEventRuleRequest" } } } @@ -135545,6 +139395,22 @@ "operationId": "extras_event_rules_retrieve", "description": "Get a event rule object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -135553,6 +139419,14 @@ }, "description": "A unique integer value identifying this event rule.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -135735,6 +139609,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -136126,6 +140008,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "file_extension", @@ -137100,6 +140990,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -137339,7 +141237,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ExportTemplateRequest" + "$ref": "#/components/schemas/BulkExportTemplateRequest" } } }, @@ -137347,7 +141245,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ExportTemplateRequest" + "$ref": "#/components/schemas/BulkExportTemplateRequest" } } } @@ -137390,7 +141288,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ExportTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkExportTemplateRequest" } } }, @@ -137398,7 +141296,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ExportTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkExportTemplateRequest" } } } @@ -137476,6 +141374,22 @@ "operationId": "extras_export_templates_retrieve", "description": "Get a export template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -137484,6 +141398,14 @@ }, "description": "A unique integer value identifying this export template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -137707,6 +141629,14 @@ "operationId": "extras_image_attachments_list", "description": "Get a list of image attachment objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -137957,6 +141887,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -138603,6 +142541,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -138736,7 +142682,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ImageAttachmentRequest" + "$ref": "#/components/schemas/BulkImageAttachmentRequest" } } }, @@ -138744,7 +142690,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ImageAttachmentRequest" + "$ref": "#/components/schemas/BulkImageAttachmentRequest" } } } @@ -138787,7 +142733,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ImageAttachmentRequest" + "$ref": "#/components/schemas/PatchedBulkImageAttachmentRequest" } } }, @@ -138795,7 +142741,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ImageAttachmentRequest" + "$ref": "#/components/schemas/PatchedBulkImageAttachmentRequest" } } } @@ -138873,6 +142819,22 @@ "operationId": "extras_image_attachments_retrieve", "description": "Get a image attachment object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -138881,6 +142843,14 @@ }, "description": "A unique integer value identifying this image attachment.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -139182,6 +143152,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created_after", @@ -139260,6 +143238,14 @@ "format": "uuid" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -139625,6 +143611,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -139806,7 +143800,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/JournalEntryRequest" + "$ref": "#/components/schemas/BulkJournalEntryRequest" } } }, @@ -139814,7 +143808,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/JournalEntryRequest" + "$ref": "#/components/schemas/BulkJournalEntryRequest" } } } @@ -139857,7 +143851,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/JournalEntryRequest" + "$ref": "#/components/schemas/PatchedBulkJournalEntryRequest" } } }, @@ -139865,7 +143859,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/JournalEntryRequest" + "$ref": "#/components/schemas/PatchedBulkJournalEntryRequest" } } } @@ -139943,6 +143937,22 @@ "operationId": "extras_journal_entries_retrieve", "description": "Get a journal entry object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -139951,6 +143961,14 @@ }, "description": "A unique integer value identifying this journal entry.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -140119,6 +144137,22 @@ "operationId": "extras_notification_groups_list", "description": "Get a list of notification group objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "name": "limit", "required": false, @@ -140137,6 +144171,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -140254,7 +144296,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/NotificationGroupRequest" + "$ref": "#/components/schemas/BulkNotificationGroupRequest" } } }, @@ -140262,7 +144304,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/NotificationGroupRequest" + "$ref": "#/components/schemas/BulkNotificationGroupRequest" } } } @@ -140305,7 +144347,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/NotificationGroupRequest" + "$ref": "#/components/schemas/PatchedBulkNotificationGroupRequest" } } }, @@ -140313,7 +144355,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/NotificationGroupRequest" + "$ref": "#/components/schemas/PatchedBulkNotificationGroupRequest" } } } @@ -140391,6 +144433,22 @@ "operationId": "extras_notification_groups_retrieve", "description": "Get a notification group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -140399,6 +144457,14 @@ }, "description": "A unique integer value identifying this notification group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -140567,6 +144633,22 @@ "operationId": "extras_notifications_list", "description": "Get a list of notification objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "name": "limit", "required": false, @@ -140585,6 +144667,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -140702,7 +144792,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/NotificationRequest" + "$ref": "#/components/schemas/BulkNotificationRequest" } } }, @@ -140710,7 +144800,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/NotificationRequest" + "$ref": "#/components/schemas/BulkNotificationRequest" } } } @@ -140753,7 +144843,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/NotificationRequest" + "$ref": "#/components/schemas/PatchedBulkNotificationRequest" } } }, @@ -140761,7 +144851,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/NotificationRequest" + "$ref": "#/components/schemas/PatchedBulkNotificationRequest" } } } @@ -140839,6 +144929,22 @@ "operationId": "extras_notifications_retrieve", "description": "Get a notification object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -140847,6 +144953,14 @@ }, "description": "A unique integer value identifying this notification.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -141015,6 +145129,14 @@ "operationId": "extras_saved_filters_list", "description": "Get a list of saved filter objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -141272,6 +145394,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -141793,6 +145923,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -142336,7 +146474,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SavedFilterRequest" + "$ref": "#/components/schemas/BulkSavedFilterRequest" } } }, @@ -142344,7 +146482,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SavedFilterRequest" + "$ref": "#/components/schemas/BulkSavedFilterRequest" } } } @@ -142387,7 +146525,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SavedFilterRequest" + "$ref": "#/components/schemas/PatchedBulkSavedFilterRequest" } } }, @@ -142395,7 +146533,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SavedFilterRequest" + "$ref": "#/components/schemas/PatchedBulkSavedFilterRequest" } } } @@ -142473,6 +146611,22 @@ "operationId": "extras_saved_filters_retrieve", "description": "Get a saved filter object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -142481,6 +146635,14 @@ }, "description": "A unique integer value identifying this saved filter.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -142649,6 +146811,22 @@ "operationId": "extras_scripts_list", "description": "Get a list of script objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -142936,6 +147114,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -143020,6 +147206,22 @@ "operationId": "extras_scripts_retrieve", "description": "Get a script object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -143028,6 +147230,14 @@ "pattern": "^[^/]+$" }, "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -143240,6 +147450,22 @@ "operationId": "extras_subscriptions_list", "description": "Get a list of subscription objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "name": "limit", "required": false, @@ -143258,6 +147484,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -143375,7 +147609,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SubscriptionRequest" + "$ref": "#/components/schemas/BulkSubscriptionRequest" } } }, @@ -143383,7 +147617,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SubscriptionRequest" + "$ref": "#/components/schemas/BulkSubscriptionRequest" } } } @@ -143426,7 +147660,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SubscriptionRequest" + "$ref": "#/components/schemas/PatchedBulkSubscriptionRequest" } } }, @@ -143434,7 +147668,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/SubscriptionRequest" + "$ref": "#/components/schemas/PatchedBulkSubscriptionRequest" } } } @@ -143512,6 +147746,22 @@ "operationId": "extras_subscriptions_retrieve", "description": "Get a subscription object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -143520,6 +147770,14 @@ }, "description": "A unique integer value identifying this subscription.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -143688,6 +147946,14 @@ "operationId": "extras_table_configs_list", "description": "Get a list of table config objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -143945,6 +148211,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -144346,6 +148620,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -144783,7 +149065,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TableConfigRequest" + "$ref": "#/components/schemas/BulkTableConfigRequest" } } }, @@ -144791,7 +149073,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TableConfigRequest" + "$ref": "#/components/schemas/BulkTableConfigRequest" } } } @@ -144834,7 +149116,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TableConfigRequest" + "$ref": "#/components/schemas/PatchedBulkTableConfigRequest" } } }, @@ -144842,7 +149124,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TableConfigRequest" + "$ref": "#/components/schemas/PatchedBulkTableConfigRequest" } } } @@ -144920,6 +149202,22 @@ "operationId": "extras_table_configs_retrieve", "description": "Get a table config object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -144928,6 +149226,14 @@ }, "description": "A unique integer value identifying this table config.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -145096,6 +149402,22 @@ "operationId": "extras_tagged_objects_list", "description": "Get a list of tagged item objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -145332,6 +149654,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -145437,6 +149767,22 @@ "operationId": "extras_tagged_objects_retrieve", "description": "Get a tagged item object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -145445,6 +149791,14 @@ }, "description": "A unique integer value identifying this tagged item.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -145477,6 +149831,14 @@ "operationId": "extras_tags_list", "description": "Get a list of tag objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "color", @@ -145903,6 +150265,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "for_object_type_id", @@ -146293,6 +150663,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -146768,7 +151146,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TagRequest" + "$ref": "#/components/schemas/BulkTagRequest" } } }, @@ -146776,7 +151154,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TagRequest" + "$ref": "#/components/schemas/BulkTagRequest" } } } @@ -146819,7 +151197,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TagRequest" + "$ref": "#/components/schemas/PatchedBulkTagRequest" } } }, @@ -146827,7 +151205,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TagRequest" + "$ref": "#/components/schemas/PatchedBulkTagRequest" } } } @@ -146905,6 +151283,22 @@ "operationId": "extras_tags_retrieve", "description": "Get a tag object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -146913,6 +151307,14 @@ }, "description": "A unique integer value identifying this tag.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -147081,6 +151483,14 @@ "operationId": "extras_webhooks_list", "description": "Get a list of webhook objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "ca_file_path", @@ -147482,6 +151892,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "http_content_type", @@ -148149,6 +152567,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -148606,7 +153032,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WebhookRequest" + "$ref": "#/components/schemas/BulkWebhookRequest" } } }, @@ -148614,7 +153040,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WebhookRequest" + "$ref": "#/components/schemas/BulkWebhookRequest" } } } @@ -148657,7 +153083,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WebhookRequest" + "$ref": "#/components/schemas/PatchedBulkWebhookRequest" } } }, @@ -148665,7 +153091,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WebhookRequest" + "$ref": "#/components/schemas/PatchedBulkWebhookRequest" } } } @@ -148743,6 +153169,22 @@ "operationId": "extras_webhooks_retrieve", "description": "Get a webhook object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -148751,6 +153193,14 @@ }, "description": "A unique integer value identifying this webhook.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -148919,6 +153369,14 @@ "operationId": "ipam_aggregates_list", "description": "Get a list of aggregate objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -149337,6 +153795,14 @@ "type": "number" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -149539,6 +154005,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -149988,7 +154462,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/AggregateRequest" + "$ref": "#/components/schemas/BulkAggregateRequest" } } }, @@ -149996,7 +154470,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/AggregateRequest" + "$ref": "#/components/schemas/BulkAggregateRequest" } } } @@ -150039,7 +154513,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/AggregateRequest" + "$ref": "#/components/schemas/PatchedBulkAggregateRequest" } } }, @@ -150047,7 +154521,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/AggregateRequest" + "$ref": "#/components/schemas/PatchedBulkAggregateRequest" } } } @@ -150125,6 +154599,22 @@ "operationId": "ipam_aggregates_retrieve", "description": "Get a aggregate object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -150133,6 +154623,14 @@ }, "description": "A unique integer value identifying this aggregate.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -150301,6 +154799,14 @@ "operationId": "ipam_asn_ranges_list", "description": "Get a list of ASN range objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -150636,6 +155142,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -150989,6 +155503,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -151653,7 +156175,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ASNRangeRequest" + "$ref": "#/components/schemas/BulkASNRangeRequest" } } }, @@ -151661,7 +156183,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ASNRangeRequest" + "$ref": "#/components/schemas/BulkASNRangeRequest" } } } @@ -151704,7 +156226,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ASNRangeRequest" + "$ref": "#/components/schemas/PatchedBulkASNRangeRequest" } } }, @@ -151712,7 +156234,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ASNRangeRequest" + "$ref": "#/components/schemas/PatchedBulkASNRangeRequest" } } } @@ -151790,6 +156312,22 @@ "operationId": "ipam_asn_ranges_retrieve", "description": "Get a ASN range object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -151798,6 +156336,14 @@ }, "description": "A unique integer value identifying this ASN range.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -151966,6 +156512,22 @@ "operationId": "ipam_asn_ranges_available_asns_list", "description": "Get a ASN object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -151973,6 +156535,14 @@ "type": "integer" }, "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -152154,6 +156724,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -152404,6 +156982,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -152606,6 +157192,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -153253,7 +157847,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ASNRequest" + "$ref": "#/components/schemas/BulkASNRequest" } } }, @@ -153261,7 +157855,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ASNRequest" + "$ref": "#/components/schemas/BulkASNRequest" } } } @@ -153304,7 +157898,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ASNRequest" + "$ref": "#/components/schemas/PatchedBulkASNRequest" } } }, @@ -153312,7 +157906,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ASNRequest" + "$ref": "#/components/schemas/PatchedBulkASNRequest" } } } @@ -153390,6 +157984,22 @@ "operationId": "ipam_asns_retrieve", "description": "Get a ASN object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -153398,6 +158008,14 @@ }, "description": "A unique integer value identifying this ASN.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -153566,6 +158184,14 @@ "operationId": "ipam_fhrp_group_assignments_list", "description": "Get a list of FHRP group assignment objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -153690,6 +158316,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group_id", @@ -154027,6 +158661,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -154262,7 +158904,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FHRPGroupAssignmentRequest" + "$ref": "#/components/schemas/BulkFHRPGroupAssignmentRequest" } } }, @@ -154270,7 +158912,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FHRPGroupAssignmentRequest" + "$ref": "#/components/schemas/BulkFHRPGroupAssignmentRequest" } } } @@ -154313,7 +158955,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FHRPGroupAssignmentRequest" + "$ref": "#/components/schemas/PatchedBulkFHRPGroupAssignmentRequest" } } }, @@ -154321,7 +158963,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FHRPGroupAssignmentRequest" + "$ref": "#/components/schemas/PatchedBulkFHRPGroupAssignmentRequest" } } } @@ -154399,6 +159041,22 @@ "operationId": "ipam_fhrp_group_assignments_retrieve", "description": "Get a FHRP group assignment object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -154407,6 +159065,14 @@ }, "description": "A unique integer value identifying this FHRP group assignment.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -154913,6 +159579,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -155163,6 +159837,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group_id", @@ -155601,6 +160283,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -156063,7 +160753,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FHRPGroupRequest" + "$ref": "#/components/schemas/BulkFHRPGroupRequest" } } }, @@ -156071,7 +160761,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FHRPGroupRequest" + "$ref": "#/components/schemas/BulkFHRPGroupRequest" } } } @@ -156114,7 +160804,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FHRPGroupRequest" + "$ref": "#/components/schemas/PatchedBulkFHRPGroupRequest" } } }, @@ -156122,7 +160812,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FHRPGroupRequest" + "$ref": "#/components/schemas/PatchedBulkFHRPGroupRequest" } } } @@ -156200,6 +160890,22 @@ "operationId": "ipam_fhrp_groups_retrieve", "description": "Get a FHRP group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -156208,6 +160914,14 @@ }, "description": "A unique integer value identifying this FHRP group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -156513,6 +161227,14 @@ }, "description": "Is assigned to an interface" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -157048,6 +161770,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -157355,6 +162085,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -158293,7 +163031,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPAddressRequest" + "$ref": "#/components/schemas/BulkIPAddressRequest" } } }, @@ -158301,7 +163039,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPAddressRequest" + "$ref": "#/components/schemas/BulkIPAddressRequest" } } } @@ -158344,7 +163082,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPAddressRequest" + "$ref": "#/components/schemas/PatchedBulkIPAddressRequest" } } }, @@ -158352,7 +163090,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPAddressRequest" + "$ref": "#/components/schemas/PatchedBulkIPAddressRequest" } } } @@ -158430,6 +163168,22 @@ "operationId": "ipam_ip_addresses_retrieve", "description": "Get a IP address object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -158438,6 +163192,14 @@ }, "description": "A unique integer value identifying this IP address.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -158606,6 +163368,14 @@ "operationId": "ipam_ip_ranges_list", "description": "Get a list of IP range objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -158959,6 +163729,14 @@ "type": "number" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -159175,6 +163953,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -159960,7 +164746,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPRangeRequest" + "$ref": "#/components/schemas/BulkIPRangeRequest" } } }, @@ -159968,7 +164754,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPRangeRequest" + "$ref": "#/components/schemas/BulkIPRangeRequest" } } } @@ -160011,7 +164797,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPRangeRequest" + "$ref": "#/components/schemas/PatchedBulkIPRangeRequest" } } }, @@ -160019,7 +164805,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPRangeRequest" + "$ref": "#/components/schemas/PatchedBulkIPRangeRequest" } } } @@ -160097,6 +164883,22 @@ "operationId": "ipam_ip_ranges_retrieve", "description": "Get a IP range object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -160105,6 +164907,14 @@ }, "description": "A unique integer value identifying this IP range.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -160273,6 +165083,22 @@ "operationId": "ipam_ip_ranges_available_ips_list", "description": "Get a IP address object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -160280,6 +165106,14 @@ "type": "integer" }, "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -160376,6 +165210,14 @@ "operationId": "ipam_prefixes_list", "description": "Get a list of prefix objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "children", @@ -160899,6 +165741,14 @@ "type": "number" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -161190,6 +166040,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -162301,7 +167159,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PrefixRequest" + "$ref": "#/components/schemas/BulkPrefixRequest" } } }, @@ -162309,7 +167167,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PrefixRequest" + "$ref": "#/components/schemas/BulkPrefixRequest" } } } @@ -162352,7 +167210,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PrefixRequest" + "$ref": "#/components/schemas/PatchedBulkPrefixRequest" } } }, @@ -162360,7 +167218,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/PrefixRequest" + "$ref": "#/components/schemas/PatchedBulkPrefixRequest" } } } @@ -162438,6 +167296,22 @@ "operationId": "ipam_prefixes_retrieve", "description": "Get a prefix object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -162446,6 +167320,14 @@ }, "description": "A unique integer value identifying this prefix.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -162614,6 +167496,22 @@ "operationId": "ipam_prefixes_available_ips_list", "description": "Get a IP address object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -162621,6 +167519,14 @@ "type": "integer" }, "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -162717,6 +167623,22 @@ "operationId": "ipam_prefixes_available_prefixes_list", "description": "Get a prefix object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -162724,6 +167646,14 @@ "type": "integer" }, "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -162820,6 +167750,14 @@ "operationId": "ipam_rirs_list", "description": "Get a list of RIR objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -163070,6 +168008,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -163430,6 +168376,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -163868,7 +168822,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RIRRequest" + "$ref": "#/components/schemas/BulkRIRRequest" } } }, @@ -163876,7 +168830,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RIRRequest" + "$ref": "#/components/schemas/BulkRIRRequest" } } } @@ -163919,7 +168873,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RIRRequest" + "$ref": "#/components/schemas/PatchedBulkRIRRequest" } } }, @@ -163927,7 +168881,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RIRRequest" + "$ref": "#/components/schemas/PatchedBulkRIRRequest" } } } @@ -164005,6 +168959,22 @@ "operationId": "ipam_rirs_retrieve", "description": "Get a RIR object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -164013,6 +168983,14 @@ }, "description": "A unique integer value identifying this RIR.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -164181,6 +169159,14 @@ "operationId": "ipam_roles_list", "description": "Get a list of role objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -164431,6 +169417,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -164784,6 +169778,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -165307,7 +170309,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RoleRequest" + "$ref": "#/components/schemas/BulkRoleRequest" } } }, @@ -165315,7 +170317,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RoleRequest" + "$ref": "#/components/schemas/BulkRoleRequest" } } } @@ -165358,7 +170360,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RoleRequest" + "$ref": "#/components/schemas/PatchedBulkRoleRequest" } } }, @@ -165366,7 +170368,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RoleRequest" + "$ref": "#/components/schemas/PatchedBulkRoleRequest" } } } @@ -165444,6 +170446,22 @@ "operationId": "ipam_roles_retrieve", "description": "Get a role object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -165452,6 +170470,14 @@ }, "description": "A unique integer value identifying this role.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -165620,6 +170646,14 @@ "operationId": "ipam_route_targets_list", "description": "Get a list of route target objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -165986,6 +171020,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -166455,6 +171497,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -166844,7 +171894,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RouteTargetRequest" + "$ref": "#/components/schemas/BulkRouteTargetRequest" } } }, @@ -166852,7 +171902,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RouteTargetRequest" + "$ref": "#/components/schemas/BulkRouteTargetRequest" } } } @@ -166895,7 +171945,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RouteTargetRequest" + "$ref": "#/components/schemas/PatchedBulkRouteTargetRequest" } } }, @@ -166903,7 +171953,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/RouteTargetRequest" + "$ref": "#/components/schemas/PatchedBulkRouteTargetRequest" } } } @@ -166981,6 +172031,22 @@ "operationId": "ipam_route_targets_retrieve", "description": "Get a route target object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -166989,6 +172055,14 @@ }, "description": "A unique integer value identifying this route target.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -167157,6 +172231,14 @@ "operationId": "ipam_service_templates_list", "description": "Get a list of application service template objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -167407,6 +172489,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -167760,6 +172850,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -168253,7 +173351,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceTemplateRequest" + "$ref": "#/components/schemas/BulkServiceTemplateRequest" } } }, @@ -168261,7 +173359,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceTemplateRequest" + "$ref": "#/components/schemas/BulkServiceTemplateRequest" } } } @@ -168304,7 +173402,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkServiceTemplateRequest" } } }, @@ -168312,7 +173410,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceTemplateRequest" + "$ref": "#/components/schemas/PatchedBulkServiceTemplateRequest" } } } @@ -168390,6 +173488,22 @@ "operationId": "ipam_service_templates_retrieve", "description": "Get a application service template object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -168398,6 +173512,14 @@ }, "description": "A unique integer value identifying this application service template.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -168566,6 +173688,14 @@ "operationId": "ipam_services_list", "description": "Get a list of application service objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -168942,6 +174072,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -169347,6 +174485,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -169974,7 +175120,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceRequest" + "$ref": "#/components/schemas/BulkServiceRequest" } } }, @@ -169982,7 +175128,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceRequest" + "$ref": "#/components/schemas/BulkServiceRequest" } } } @@ -170025,7 +175171,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceRequest" + "$ref": "#/components/schemas/PatchedBulkServiceRequest" } } }, @@ -170033,7 +175179,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceRequest" + "$ref": "#/components/schemas/PatchedBulkServiceRequest" } } } @@ -170111,6 +175257,22 @@ "operationId": "ipam_services_retrieve", "description": "Get a application service object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -170119,6 +175281,14 @@ }, "description": "A unique integer value identifying this application service.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -170287,6 +175457,14 @@ "operationId": "ipam_vlan_groups_list", "description": "Get a list of VLAN group objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cluster", @@ -170558,6 +175736,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -170918,6 +176104,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -171687,7 +176881,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANGroupRequest" + "$ref": "#/components/schemas/BulkVLANGroupRequest" } } }, @@ -171695,7 +176889,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANGroupRequest" + "$ref": "#/components/schemas/BulkVLANGroupRequest" } } } @@ -171738,7 +176932,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANGroupRequest" + "$ref": "#/components/schemas/PatchedBulkVLANGroupRequest" } } }, @@ -171746,7 +176940,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANGroupRequest" + "$ref": "#/components/schemas/PatchedBulkVLANGroupRequest" } } } @@ -171824,6 +177018,22 @@ "operationId": "ipam_vlan_groups_retrieve", "description": "Get a VLAN group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -171832,6 +177042,14 @@ }, "description": "A unique integer value identifying this VLAN group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -172000,6 +177218,22 @@ "operationId": "ipam_vlan_groups_available_vlans_list", "description": "Get a VLAN object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -172007,6 +177241,14 @@ "type": "integer" }, "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -172103,6 +177345,14 @@ "operationId": "ipam_vlan_translation_policies_list", "description": "Get a list of VLAN translation policy objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -172353,6 +177603,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -172706,6 +177964,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -172993,7 +178259,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANTranslationPolicyRequest" + "$ref": "#/components/schemas/BulkVLANTranslationPolicyRequest" } } }, @@ -173001,7 +178267,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANTranslationPolicyRequest" + "$ref": "#/components/schemas/BulkVLANTranslationPolicyRequest" } } } @@ -173044,7 +178310,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANTranslationPolicyRequest" + "$ref": "#/components/schemas/PatchedBulkVLANTranslationPolicyRequest" } } }, @@ -173052,7 +178318,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANTranslationPolicyRequest" + "$ref": "#/components/schemas/PatchedBulkVLANTranslationPolicyRequest" } } } @@ -173130,6 +178396,22 @@ "operationId": "ipam_vlan_translation_policies_retrieve", "description": "Get a VLAN translation policy object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -173138,6 +178420,14 @@ }, "description": "A unique integer value identifying this VLAN translation policy.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -173306,6 +178596,14 @@ "operationId": "ipam_vlan_translation_rules_list", "description": "Get a list of VLAN translation rule objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -173556,6 +178854,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -173843,6 +179149,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -174161,7 +179475,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANTranslationRuleRequest" + "$ref": "#/components/schemas/BulkVLANTranslationRuleRequest" } } }, @@ -174169,7 +179483,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANTranslationRuleRequest" + "$ref": "#/components/schemas/BulkVLANTranslationRuleRequest" } } } @@ -174212,7 +179526,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANTranslationRuleRequest" + "$ref": "#/components/schemas/PatchedBulkVLANTranslationRuleRequest" } } }, @@ -174220,7 +179534,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANTranslationRuleRequest" + "$ref": "#/components/schemas/PatchedBulkVLANTranslationRuleRequest" } } } @@ -174298,6 +179612,22 @@ "operationId": "ipam_vlan_translation_rules_retrieve", "description": "Get a VLAN translation rule object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -174306,6 +179636,14 @@ }, "description": "A unique integer value identifying this VLAN translation rule.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -174495,6 +179833,14 @@ "type": "string" } }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -174745,6 +180091,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -175219,6 +180573,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -176397,7 +181759,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANRequest" + "$ref": "#/components/schemas/BulkVLANRequest" } } }, @@ -176405,7 +181767,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANRequest" + "$ref": "#/components/schemas/BulkVLANRequest" } } } @@ -176448,7 +181810,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANRequest" + "$ref": "#/components/schemas/PatchedBulkVLANRequest" } } }, @@ -176456,7 +181818,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VLANRequest" + "$ref": "#/components/schemas/PatchedBulkVLANRequest" } } } @@ -176534,6 +181896,22 @@ "operationId": "ipam_vlans_retrieve", "description": "Get a VLAN object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -176542,6 +181920,14 @@ }, "description": "A unique integer value identifying this VLAN.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -176710,6 +182096,14 @@ "operationId": "ipam_vrfs_list", "description": "Get a list of VRF objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -177019,6 +182413,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -177424,6 +182826,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -177964,7 +183374,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VRFRequest" + "$ref": "#/components/schemas/BulkVRFRequest" } } }, @@ -177972,7 +183382,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VRFRequest" + "$ref": "#/components/schemas/BulkVRFRequest" } } } @@ -178015,7 +183425,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VRFRequest" + "$ref": "#/components/schemas/PatchedBulkVRFRequest" } } }, @@ -178023,7 +183433,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VRFRequest" + "$ref": "#/components/schemas/PatchedBulkVRFRequest" } } } @@ -178101,6 +183511,22 @@ "operationId": "ipam_vrfs_retrieve", "description": "Get a VRF object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -178109,6 +183535,14 @@ }, "description": "A unique integer value identifying this VRF.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -178277,6 +183711,22 @@ "operationId": "schema_retrieve", "description": "OpenApi3 schema for this API. Format can be selected via content negotiation.\n\n- YAML: application/vnd.oai.openapi\n- JSON: application/vnd.oai.openapi+json", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "format", @@ -178312,6 +183762,14 @@ "zh" ] } + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -178363,6 +183821,32 @@ "get": { "operationId": "status_retrieve", "description": "A lightweight read-only endpoint for conveying NetBox's current operational status.", + "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + } + ], "tags": [ "status" ], @@ -178394,6 +183878,14 @@ "operationId": "tenancy_contact_assignments_list", "description": "Get a list of contact assignment objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact_id", @@ -178519,6 +184011,14 @@ "format": "uuid" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -178892,6 +184392,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -179286,7 +184794,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactAssignmentRequest" + "$ref": "#/components/schemas/BulkContactAssignmentRequest" } } }, @@ -179294,7 +184802,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactAssignmentRequest" + "$ref": "#/components/schemas/BulkContactAssignmentRequest" } } } @@ -179337,7 +184845,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactAssignmentRequest" + "$ref": "#/components/schemas/PatchedBulkContactAssignmentRequest" } } }, @@ -179345,7 +184853,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactAssignmentRequest" + "$ref": "#/components/schemas/PatchedBulkContactAssignmentRequest" } } } @@ -179423,6 +184931,22 @@ "operationId": "tenancy_contact_assignments_retrieve", "description": "Get a contact assignment object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -179431,6 +184955,14 @@ }, "description": "A unique integer value identifying this contact assignment.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -179647,6 +185179,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact_id", @@ -179923,6 +185463,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -180276,6 +185824,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -180768,7 +186324,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactGroupRequest" + "$ref": "#/components/schemas/BulkContactGroupRequest" } } }, @@ -180776,7 +186332,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactGroupRequest" + "$ref": "#/components/schemas/BulkContactGroupRequest" } } } @@ -180819,7 +186375,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactGroupRequest" + "$ref": "#/components/schemas/PatchedBulkContactGroupRequest" } } }, @@ -180827,7 +186383,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactGroupRequest" + "$ref": "#/components/schemas/PatchedBulkContactGroupRequest" } } } @@ -180905,6 +186461,22 @@ "operationId": "tenancy_contact_groups_retrieve", "description": "Get a contact group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -180913,6 +186485,14 @@ }, "description": "A unique integer value identifying this contact group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -181081,6 +186661,14 @@ "operationId": "tenancy_contact_roles_list", "description": "Get a list of contact role objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -181331,6 +186919,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -181684,6 +187280,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -182122,7 +187726,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactRoleRequest" + "$ref": "#/components/schemas/BulkContactRoleRequest" } } }, @@ -182130,7 +187734,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactRoleRequest" + "$ref": "#/components/schemas/BulkContactRoleRequest" } } } @@ -182173,7 +187777,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactRoleRequest" + "$ref": "#/components/schemas/PatchedBulkContactRoleRequest" } } }, @@ -182181,7 +187785,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactRoleRequest" + "$ref": "#/components/schemas/PatchedBulkContactRoleRequest" } } } @@ -182259,6 +187863,22 @@ "operationId": "tenancy_contact_roles_retrieve", "description": "Get a contact role object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -182267,6 +187887,14 @@ }, "description": "A unique integer value identifying this contact role.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -182586,6 +188214,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -182987,6 +188623,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -183539,6 +189183,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -184128,7 +189780,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactRequest" + "$ref": "#/components/schemas/BulkContactRequest" } } }, @@ -184136,7 +189788,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactRequest" + "$ref": "#/components/schemas/BulkContactRequest" } } } @@ -184179,7 +189831,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactRequest" + "$ref": "#/components/schemas/PatchedBulkContactRequest" } } }, @@ -184187,7 +189839,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ContactRequest" + "$ref": "#/components/schemas/PatchedBulkContactRequest" } } } @@ -184265,6 +189917,22 @@ "operationId": "tenancy_contacts_retrieve", "description": "Get a contact object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -184273,6 +189941,14 @@ }, "description": "A unique integer value identifying this contact.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -184489,6 +190165,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -184739,6 +190423,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -185092,6 +190784,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -185584,7 +191284,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TenantGroupRequest" + "$ref": "#/components/schemas/BulkTenantGroupRequest" } } }, @@ -185592,7 +191292,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TenantGroupRequest" + "$ref": "#/components/schemas/BulkTenantGroupRequest" } } } @@ -185635,7 +191335,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TenantGroupRequest" + "$ref": "#/components/schemas/PatchedBulkTenantGroupRequest" } } }, @@ -185643,7 +191343,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TenantGroupRequest" + "$ref": "#/components/schemas/PatchedBulkTenantGroupRequest" } } } @@ -185721,6 +191421,22 @@ "operationId": "tenancy_tenant_groups_retrieve", "description": "Get a tenant group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -185729,6 +191445,14 @@ }, "description": "A unique integer value identifying this tenant group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -185897,6 +191621,14 @@ "operationId": "tenancy_tenants_list", "description": "Get a list of tenant objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -186223,6 +191955,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -186624,6 +192364,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -187062,7 +192810,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TenantRequest" + "$ref": "#/components/schemas/BulkTenantRequest" } } }, @@ -187070,7 +192818,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TenantRequest" + "$ref": "#/components/schemas/BulkTenantRequest" } } } @@ -187113,7 +192861,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TenantRequest" + "$ref": "#/components/schemas/PatchedBulkTenantRequest" } } }, @@ -187121,7 +192869,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TenantRequest" + "$ref": "#/components/schemas/PatchedBulkTenantRequest" } } } @@ -187199,6 +192947,22 @@ "operationId": "tenancy_tenants_retrieve", "description": "Get a tenant object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -187207,6 +192971,14 @@ }, "description": "A unique integer value identifying this tenant.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -187374,6 +193146,32 @@ "get": { "operationId": "users_config_retrieve", "description": "An API endpoint via which a user can update his or her own UserConfig data (but no one else's).", + "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + } + ], "tags": [ "users" ], @@ -187405,6 +193203,14 @@ "operationId": "users_groups_list", "description": "Get a list of group objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "description", @@ -187556,6 +193362,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -187836,6 +193650,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -188065,7 +193887,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/GroupRequest" + "$ref": "#/components/schemas/BulkGroupRequest" } } }, @@ -188073,7 +193895,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/GroupRequest" + "$ref": "#/components/schemas/BulkGroupRequest" } } } @@ -188116,7 +193938,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/GroupRequest" + "$ref": "#/components/schemas/PatchedBulkGroupRequest" } } }, @@ -188124,7 +193946,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/GroupRequest" + "$ref": "#/components/schemas/PatchedBulkGroupRequest" } } } @@ -188202,6 +194024,22 @@ "operationId": "users_groups_retrieve", "description": "Get a group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -188210,6 +194048,14 @@ }, "description": "A unique integer value identifying this group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -188378,6 +194224,14 @@ "operationId": "users_owner_groups_list", "description": "Get a list of owner group objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "description", @@ -188529,6 +194383,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -188783,6 +194645,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -188908,7 +194778,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/OwnerGroupRequest" + "$ref": "#/components/schemas/BulkOwnerGroupRequest" } } }, @@ -188916,7 +194786,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/OwnerGroupRequest" + "$ref": "#/components/schemas/BulkOwnerGroupRequest" } } } @@ -188959,7 +194829,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/OwnerGroupRequest" + "$ref": "#/components/schemas/PatchedBulkOwnerGroupRequest" } } }, @@ -188967,7 +194837,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/OwnerGroupRequest" + "$ref": "#/components/schemas/PatchedBulkOwnerGroupRequest" } } } @@ -189045,6 +194915,22 @@ "operationId": "users_owner_groups_retrieve", "description": "Get a owner group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -189053,6 +194939,14 @@ }, "description": "A unique integer value identifying this owner group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -189221,6 +195115,14 @@ "operationId": "users_owners_list", "description": "Get a list of owner objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "description", @@ -189372,6 +195274,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -189680,6 +195590,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -189909,7 +195827,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/OwnerRequest" + "$ref": "#/components/schemas/BulkOwnerRequest" } } }, @@ -189917,7 +195835,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/OwnerRequest" + "$ref": "#/components/schemas/BulkOwnerRequest" } } } @@ -189960,7 +195878,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/OwnerRequest" + "$ref": "#/components/schemas/PatchedBulkOwnerRequest" } } }, @@ -189968,7 +195886,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/OwnerRequest" + "$ref": "#/components/schemas/PatchedBulkOwnerRequest" } } } @@ -190046,6 +195964,22 @@ "operationId": "users_owners_retrieve", "description": "Get a owner object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -190054,6 +195988,14 @@ }, "description": "A unique integer value identifying this owner.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -190222,6 +196164,14 @@ "operationId": "users_permissions_list", "description": "Get a list of permission objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "can_add", @@ -190408,6 +196358,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -190906,6 +196864,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -191083,7 +197049,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ObjectPermissionRequest" + "$ref": "#/components/schemas/BulkObjectPermissionRequest" } } }, @@ -191091,7 +197057,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ObjectPermissionRequest" + "$ref": "#/components/schemas/BulkObjectPermissionRequest" } } } @@ -191134,7 +197100,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ObjectPermissionRequest" + "$ref": "#/components/schemas/PatchedBulkObjectPermissionRequest" } } }, @@ -191142,7 +197108,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ObjectPermissionRequest" + "$ref": "#/components/schemas/PatchedBulkObjectPermissionRequest" } } } @@ -191220,6 +197186,22 @@ "operationId": "users_permissions_retrieve", "description": "Get a permission object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -191228,6 +197210,14 @@ }, "description": "A unique integer value identifying this permission.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -191396,6 +197386,14 @@ "operationId": "users_tokens_list", "description": "Get a list of token objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -191602,6 +197600,14 @@ "format": "date-time" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -191880,6 +197886,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -192307,7 +198321,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TokenRequest" + "$ref": "#/components/schemas/BulkTokenRequest" } } }, @@ -192315,7 +198329,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TokenRequest" + "$ref": "#/components/schemas/BulkTokenRequest" } } } @@ -192358,7 +198372,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TokenRequest" + "$ref": "#/components/schemas/PatchedBulkTokenRequest" } } }, @@ -192366,7 +198380,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TokenRequest" + "$ref": "#/components/schemas/PatchedBulkTokenRequest" } } } @@ -192444,6 +198458,22 @@ "operationId": "users_tokens_retrieve", "description": "Get a token object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -192452,6 +198482,14 @@ }, "description": "A unique integer value identifying this token.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -192675,6 +198713,14 @@ "operationId": "users_users_list", "description": "Get a list of user objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "date_joined", @@ -192911,6 +198957,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "first_name", @@ -193493,6 +199547,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -193847,7 +199909,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UserRequest" + "$ref": "#/components/schemas/BulkUserRequest" } } }, @@ -193855,7 +199917,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UserRequest" + "$ref": "#/components/schemas/BulkUserRequest" } } } @@ -193898,7 +199960,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UserRequest" + "$ref": "#/components/schemas/PatchedBulkUserRequest" } } }, @@ -193906,7 +199968,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UserRequest" + "$ref": "#/components/schemas/PatchedBulkUserRequest" } } } @@ -193984,6 +200046,22 @@ "operationId": "users_users_retrieve", "description": "Get a user object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -193992,6 +200070,14 @@ }, "description": "A unique integer value identifying this user.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -194160,6 +200246,14 @@ "operationId": "virtualization_cluster_groups_list", "description": "Get a list of cluster group objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -194486,6 +200580,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -194839,6 +200941,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -195277,7 +201387,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterGroupRequest" + "$ref": "#/components/schemas/BulkClusterGroupRequest" } } }, @@ -195285,7 +201395,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterGroupRequest" + "$ref": "#/components/schemas/BulkClusterGroupRequest" } } } @@ -195328,7 +201438,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterGroupRequest" + "$ref": "#/components/schemas/PatchedBulkClusterGroupRequest" } } }, @@ -195336,7 +201446,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterGroupRequest" + "$ref": "#/components/schemas/PatchedBulkClusterGroupRequest" } } } @@ -195414,6 +201524,22 @@ "operationId": "virtualization_cluster_groups_retrieve", "description": "Get a cluster group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -195422,6 +201548,14 @@ }, "description": "A unique integer value identifying this cluster group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -195590,6 +201724,14 @@ "operationId": "virtualization_cluster_types_list", "description": "Get a list of cluster type objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -195840,6 +201982,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -196193,6 +202343,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -196631,7 +202789,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterTypeRequest" + "$ref": "#/components/schemas/BulkClusterTypeRequest" } } }, @@ -196639,7 +202797,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterTypeRequest" + "$ref": "#/components/schemas/BulkClusterTypeRequest" } } } @@ -196682,7 +202840,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterTypeRequest" + "$ref": "#/components/schemas/PatchedBulkClusterTypeRequest" } } }, @@ -196690,7 +202848,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterTypeRequest" + "$ref": "#/components/schemas/PatchedBulkClusterTypeRequest" } } } @@ -196768,6 +202926,22 @@ "operationId": "virtualization_cluster_types_retrieve", "description": "Get a cluster type object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -196776,6 +202950,14 @@ }, "description": "A unique integer value identifying this cluster type.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -196944,6 +203126,14 @@ "operationId": "virtualization_clusters_list", "description": "Get a list of cluster objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -197270,6 +203460,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -197725,6 +203923,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -198586,7 +204792,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterRequest" + "$ref": "#/components/schemas/BulkClusterRequest" } } }, @@ -198594,7 +204800,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterRequest" + "$ref": "#/components/schemas/BulkClusterRequest" } } } @@ -198637,7 +204843,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterRequest" + "$ref": "#/components/schemas/PatchedBulkClusterRequest" } } }, @@ -198645,7 +204851,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/ClusterRequest" + "$ref": "#/components/schemas/PatchedBulkClusterRequest" } } } @@ -198723,6 +204929,22 @@ "operationId": "virtualization_clusters_retrieve", "description": "Get a cluster object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -198731,6 +204953,14 @@ }, "description": "A unique integer value identifying this cluster.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -198925,6 +205155,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cluster", @@ -199234,6 +205472,14 @@ "type": "boolean" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -200064,6 +206310,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -200605,7 +206859,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VMInterfaceRequest" + "$ref": "#/components/schemas/BulkVMInterfaceRequest" } } }, @@ -200613,7 +206867,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VMInterfaceRequest" + "$ref": "#/components/schemas/BulkVMInterfaceRequest" } } } @@ -200656,7 +206910,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VMInterfaceRequest" + "$ref": "#/components/schemas/PatchedBulkVMInterfaceRequest" } } }, @@ -200664,7 +206918,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VMInterfaceRequest" + "$ref": "#/components/schemas/PatchedBulkVMInterfaceRequest" } } } @@ -200742,6 +206996,22 @@ "operationId": "virtualization_interfaces_retrieve", "description": "Get a interface object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -200750,6 +207020,14 @@ }, "description": "A unique integer value identifying this interface.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -200918,6 +207196,14 @@ "operationId": "virtualization_virtual_disks_list", "description": "Get a list of virtual disk objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -201168,6 +207454,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -201521,6 +207815,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -201945,7 +208247,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualDiskRequest" + "$ref": "#/components/schemas/BulkVirtualDiskRequest" } } }, @@ -201953,7 +208255,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualDiskRequest" + "$ref": "#/components/schemas/BulkVirtualDiskRequest" } } } @@ -201996,7 +208298,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualDiskRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualDiskRequest" } } }, @@ -202004,7 +208306,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualDiskRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualDiskRequest" } } } @@ -202082,6 +208384,22 @@ "operationId": "virtualization_virtual_disks_retrieve", "description": "Get a virtual disk object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -202090,6 +208408,14 @@ }, "description": "A unique integer value identifying this virtual disk.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -202258,6 +208584,14 @@ "operationId": "virtualization_virtual_machine_types_list", "description": "Get a list of virtual machine type objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -202726,6 +209060,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -203079,6 +209421,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -203602,7 +209952,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualMachineTypeRequest" + "$ref": "#/components/schemas/BulkVirtualMachineTypeRequest" } } }, @@ -203610,7 +209960,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualMachineTypeRequest" + "$ref": "#/components/schemas/BulkVirtualMachineTypeRequest" } } } @@ -203653,7 +210003,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualMachineTypeRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualMachineTypeRequest" } } }, @@ -203661,7 +210011,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualMachineTypeRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualMachineTypeRequest" } } } @@ -203739,6 +210089,22 @@ "operationId": "virtualization_virtual_machine_types_retrieve", "description": "Get a virtual machine type object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -203747,6 +210113,14 @@ }, "description": "A unique integer value identifying this virtual machine type.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -203915,6 +210289,14 @@ "operationId": "virtualization_virtual_machines_list", "description": "Get a list of virtual machine objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "cluster", @@ -204568,6 +210950,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "has_primary_ip", @@ -205251,6 +211641,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -206693,7 +213091,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualMachineWithConfigContextRequest" + "$ref": "#/components/schemas/BulkVirtualMachineWithConfigContextRequest" } } }, @@ -206701,7 +213099,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualMachineWithConfigContextRequest" + "$ref": "#/components/schemas/BulkVirtualMachineWithConfigContextRequest" } } } @@ -206744,7 +213142,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualMachineWithConfigContextRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualMachineWithConfigContextRequest" } } }, @@ -206752,7 +213150,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/VirtualMachineWithConfigContextRequest" + "$ref": "#/components/schemas/PatchedBulkVirtualMachineWithConfigContextRequest" } } } @@ -206830,6 +213228,22 @@ "operationId": "virtualization_virtual_machines_retrieve", "description": "Get a virtual machine object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -206838,6 +213252,14 @@ }, "description": "A unique integer value identifying this virtual machine.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -207110,6 +213532,14 @@ "operationId": "vpn_ike_policies_list", "description": "Get a list of IKE policy objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -207360,6 +213790,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -207936,6 +214374,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -208463,7 +214909,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IKEPolicyRequest" + "$ref": "#/components/schemas/BulkIKEPolicyRequest" } } }, @@ -208471,7 +214917,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IKEPolicyRequest" + "$ref": "#/components/schemas/BulkIKEPolicyRequest" } } } @@ -208514,7 +214960,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IKEPolicyRequest" + "$ref": "#/components/schemas/PatchedBulkIKEPolicyRequest" } } }, @@ -208522,7 +214968,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IKEPolicyRequest" + "$ref": "#/components/schemas/PatchedBulkIKEPolicyRequest" } } } @@ -208600,6 +215046,22 @@ "operationId": "vpn_ike_policies_retrieve", "description": "Get a IKE policy object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -208608,6 +215070,14 @@ }, "description": "A unique integer value identifying this IKE policy.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -209114,6 +215584,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -209527,6 +216005,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -210100,6 +216586,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -210472,7 +216966,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IKEProposalRequest" + "$ref": "#/components/schemas/BulkIKEProposalRequest" } } }, @@ -210480,7 +216974,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IKEProposalRequest" + "$ref": "#/components/schemas/BulkIKEProposalRequest" } } } @@ -210523,7 +217017,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IKEProposalRequest" + "$ref": "#/components/schemas/PatchedBulkIKEProposalRequest" } } }, @@ -210531,7 +217025,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IKEProposalRequest" + "$ref": "#/components/schemas/PatchedBulkIKEProposalRequest" } } } @@ -210609,6 +217103,22 @@ "operationId": "vpn_ike_proposals_retrieve", "description": "Get a IKE proposal object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -210617,6 +217127,14 @@ }, "description": "A unique integer value identifying this IKE proposal.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -210785,6 +217303,14 @@ "operationId": "vpn_ipsec_policies_list", "description": "Get a list of IPSec policy objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -211035,6 +217561,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -211436,6 +217970,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -211903,7 +218445,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecPolicyRequest" + "$ref": "#/components/schemas/BulkIPSecPolicyRequest" } } }, @@ -211911,7 +218453,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecPolicyRequest" + "$ref": "#/components/schemas/BulkIPSecPolicyRequest" } } } @@ -211954,7 +218496,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecPolicyRequest" + "$ref": "#/components/schemas/PatchedBulkIPSecPolicyRequest" } } }, @@ -211962,7 +218504,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecPolicyRequest" + "$ref": "#/components/schemas/PatchedBulkIPSecPolicyRequest" } } } @@ -212040,6 +218582,22 @@ "operationId": "vpn_ipsec_policies_retrieve", "description": "Get a IPSec policy object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -212048,6 +218606,14 @@ }, "description": "A unique integer value identifying this IPSec policy.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -212216,6 +218782,14 @@ "operationId": "vpn_ipsec_profiles_list", "description": "Get a list of IPSec profile objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -212466,6 +219040,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -213086,6 +219668,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -213373,7 +219963,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecProfileRequest" + "$ref": "#/components/schemas/BulkIPSecProfileRequest" } } }, @@ -213381,7 +219971,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecProfileRequest" + "$ref": "#/components/schemas/BulkIPSecProfileRequest" } } } @@ -213424,7 +220014,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecProfileRequest" + "$ref": "#/components/schemas/PatchedBulkIPSecProfileRequest" } } }, @@ -213432,7 +220022,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecProfileRequest" + "$ref": "#/components/schemas/PatchedBulkIPSecProfileRequest" } } } @@ -213510,6 +220100,22 @@ "operationId": "vpn_ipsec_profiles_retrieve", "description": "Get a IPSec profile object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -213518,6 +220124,14 @@ }, "description": "A unique integer value identifying this IPSec profile.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -213873,6 +220487,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -214310,6 +220932,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -214715,6 +221345,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -215172,7 +221810,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecProposalRequest" + "$ref": "#/components/schemas/BulkIPSecProposalRequest" } } }, @@ -215180,7 +221818,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecProposalRequest" + "$ref": "#/components/schemas/BulkIPSecProposalRequest" } } } @@ -215223,7 +221861,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecProposalRequest" + "$ref": "#/components/schemas/PatchedBulkIPSecProposalRequest" } } }, @@ -215231,7 +221869,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/IPSecProposalRequest" + "$ref": "#/components/schemas/PatchedBulkIPSecProposalRequest" } } } @@ -215309,6 +221947,22 @@ "operationId": "vpn_ipsec_proposals_retrieve", "description": "Get a IPSec proposal object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -215317,6 +221971,14 @@ }, "description": "A unique integer value identifying this IPSec proposal.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -215618,6 +222280,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -215771,6 +222441,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -216077,6 +222755,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -216520,7 +223206,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/L2VPNTerminationRequest" + "$ref": "#/components/schemas/BulkL2VPNTerminationRequest" } } }, @@ -216528,7 +223214,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/L2VPNTerminationRequest" + "$ref": "#/components/schemas/BulkL2VPNTerminationRequest" } } } @@ -216571,7 +223257,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/L2VPNTerminationRequest" + "$ref": "#/components/schemas/PatchedBulkL2VPNTerminationRequest" } } }, @@ -216579,7 +223265,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/L2VPNTerminationRequest" + "$ref": "#/components/schemas/PatchedBulkL2VPNTerminationRequest" } } } @@ -216657,6 +223343,22 @@ "operationId": "vpn_l2vpn_terminations_retrieve", "description": "Get a L2VPN termination object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -216665,6 +223367,14 @@ }, "description": "A unique integer value identifying this L2VPN termination.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -216833,6 +223543,14 @@ "operationId": "vpn_l2vpns_list", "description": "Get a list of L2VPN objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -217211,6 +223929,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -217701,6 +224427,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -218567,7 +225301,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/L2VPNRequest" + "$ref": "#/components/schemas/BulkL2VPNRequest" } } }, @@ -218575,7 +225309,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/L2VPNRequest" + "$ref": "#/components/schemas/BulkL2VPNRequest" } } } @@ -218618,7 +225352,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/L2VPNRequest" + "$ref": "#/components/schemas/PatchedBulkL2VPNRequest" } } }, @@ -218626,7 +225360,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/L2VPNRequest" + "$ref": "#/components/schemas/PatchedBulkL2VPNRequest" } } } @@ -218704,6 +225438,22 @@ "operationId": "vpn_l2vpns_retrieve", "description": "Get a L2VPN object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -218712,6 +225462,14 @@ }, "description": "A unique integer value identifying this L2VPN.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -218880,6 +225638,14 @@ "operationId": "vpn_tunnel_groups_list", "description": "Get a list of tunnel group objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -219206,6 +225972,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -219559,6 +226333,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -219997,7 +226779,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelGroupRequest" + "$ref": "#/components/schemas/BulkTunnelGroupRequest" } } }, @@ -220005,7 +226787,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelGroupRequest" + "$ref": "#/components/schemas/BulkTunnelGroupRequest" } } } @@ -220048,7 +226830,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelGroupRequest" + "$ref": "#/components/schemas/PatchedBulkTunnelGroupRequest" } } }, @@ -220056,7 +226838,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelGroupRequest" + "$ref": "#/components/schemas/PatchedBulkTunnelGroupRequest" } } } @@ -220134,6 +226916,22 @@ "operationId": "vpn_tunnel_groups_retrieve", "description": "Get a tunnel group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -220142,6 +226940,14 @@ }, "description": "A unique integer value identifying this tunnel group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -220310,6 +227116,14 @@ "operationId": "vpn_tunnel_terminations_list", "description": "Get a list of tunnel termination objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -220409,6 +227223,14 @@ "format": "uuid" } }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -220663,6 +227485,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -221246,7 +228076,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelTerminationRequest" + "$ref": "#/components/schemas/BulkTunnelTerminationRequest" } } }, @@ -221254,7 +228084,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelTerminationRequest" + "$ref": "#/components/schemas/BulkTunnelTerminationRequest" } } } @@ -221297,7 +228127,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelTerminationRequest" + "$ref": "#/components/schemas/PatchedBulkTunnelTerminationRequest" } } }, @@ -221305,7 +228135,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelTerminationRequest" + "$ref": "#/components/schemas/PatchedBulkTunnelTerminationRequest" } } } @@ -221383,6 +228213,22 @@ "operationId": "vpn_tunnel_terminations_retrieve", "description": "Get a tunnel termination object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -221391,6 +228237,14 @@ }, "description": "A unique integer value identifying this tunnel termination.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -221559,6 +228413,14 @@ "operationId": "vpn_tunnels_list", "description": "Get a list of tunnel objects.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "contact", @@ -222048,6 +228910,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -222509,6 +229379,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -223146,7 +230024,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelRequest" + "$ref": "#/components/schemas/BulkTunnelRequest" } } }, @@ -223154,7 +230032,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelRequest" + "$ref": "#/components/schemas/BulkTunnelRequest" } } } @@ -223197,7 +230075,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelRequest" + "$ref": "#/components/schemas/PatchedBulkTunnelRequest" } } }, @@ -223205,7 +230083,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/TunnelRequest" + "$ref": "#/components/schemas/PatchedBulkTunnelRequest" } } } @@ -223283,6 +230161,22 @@ "operationId": "vpn_tunnels_retrieve", "description": "Get a tunnel object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -223291,6 +230185,14 @@ }, "description": "A unique integer value identifying this tunnel.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -223507,6 +230409,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -223757,6 +230667,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -224110,6 +231028,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -224598,7 +231524,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLANGroupRequest" + "$ref": "#/components/schemas/BulkWirelessLANGroupRequest" } } }, @@ -224606,7 +231532,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLANGroupRequest" + "$ref": "#/components/schemas/BulkWirelessLANGroupRequest" } } } @@ -224649,7 +231575,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLANGroupRequest" + "$ref": "#/components/schemas/PatchedBulkWirelessLANGroupRequest" } } }, @@ -224657,7 +231583,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLANGroupRequest" + "$ref": "#/components/schemas/PatchedBulkWirelessLANGroupRequest" } } } @@ -224735,6 +231661,22 @@ "operationId": "wireless_wireless_lan_groups_retrieve", "description": "Get a wireless LAN group object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -224743,6 +231685,14 @@ }, "description": "A unique integer value identifying this wireless LAN group.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -225436,6 +232386,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -225686,6 +232644,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "group", @@ -226008,6 +232974,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -226994,7 +233968,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLANRequest" + "$ref": "#/components/schemas/BulkWirelessLANRequest" } } }, @@ -227002,7 +233976,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLANRequest" + "$ref": "#/components/schemas/BulkWirelessLANRequest" } } } @@ -227045,7 +234019,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLANRequest" + "$ref": "#/components/schemas/PatchedBulkWirelessLANRequest" } } }, @@ -227053,7 +234027,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLANRequest" + "$ref": "#/components/schemas/PatchedBulkWirelessLANRequest" } } } @@ -227131,6 +234105,22 @@ "operationId": "wireless_wireless_lans_retrieve", "description": "Get a wireless LAN object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -227139,6 +234129,14 @@ }, "description": "A unique integer value identifying this wireless LAN.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -227832,6 +234830,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, { "in": "query", "name": "created", @@ -228328,6 +235334,14 @@ "explode": true, "style": "form" }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "query", "name": "id", @@ -228578,6 +235592,14 @@ "type": "integer" } }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." + }, { "name": "ordering", "required": false, @@ -229281,7 +236303,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLinkRequest" + "$ref": "#/components/schemas/BulkWirelessLinkRequest" } } }, @@ -229289,7 +236311,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLinkRequest" + "$ref": "#/components/schemas/BulkWirelessLinkRequest" } } } @@ -229332,7 +236354,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLinkRequest" + "$ref": "#/components/schemas/PatchedBulkWirelessLinkRequest" } } }, @@ -229340,7 +236362,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/WirelessLinkRequest" + "$ref": "#/components/schemas/PatchedBulkWirelessLinkRequest" } } } @@ -229418,6 +236440,22 @@ "operationId": "wireless_wireless_links_retrieve", "description": "Get a wireless link object.", "parameters": [ + { + "in": "query", + "name": "brief", + "schema": { + "type": "boolean" + }, + "description": "Return only brief fields for each object." + }, + { + "in": "query", + "name": "fields", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response. Example: `fields=id,name`." + }, { "in": "path", "name": "id", @@ -229426,6 +236464,14 @@ }, "description": "A unique integer value identifying this wireless link.", "required": true + }, + { + "in": "query", + "name": "omit", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to exclude from the response. Example: `omit=description,tags`." } ], "tags": [ @@ -235168,6 +242214,13232 @@ "slug" ] }, + "BulkASNRangeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "rir": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefRIRRequest" + } + ] + }, + "start": { + "type": "integer", + "maximum": 4294967295, + "minimum": 1, + "format": "int64" + }, + "end": { + "type": "integer", + "maximum": 4294967295, + "minimum": 1, + "format": "int64" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "end", + "id", + "name", + "rir", + "slug", + "start" + ] + }, + "BulkASNRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "asn": { + "type": "integer", + "maximum": 4294967295, + "minimum": 1, + "format": "int64", + "description": "16- or 32-bit autonomous system number" + }, + "rir": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRIRRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "sites": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "asn", + "id" + ] + }, + "BulkAggregateRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "prefix": { + "type": "string", + "minLength": 1 + }, + "rir": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefRIRRequest" + } + ] + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "date_added": { + "type": "string", + "format": "date", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "prefix", + "rir" + ] + }, + "BulkBookmarkRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + } + }, + "required": [ + "id", + "object_id", + "object_type", + "user" + ] + }, + "BulkCableBundleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkCableRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "cat3", + "cat5", + "cat5e", + "cat6", + "cat6a", + "cat7", + "cat7a", + "cat8", + "mrj21-trunk", + "dac-active", + "dac-passive", + "coaxial", + "rg-6", + "rg-8", + "rg-11", + "rg-59", + "rg-62", + "rg-213", + "lmr-100", + "lmr-200", + "lmr-400", + "mmf", + "mmf-om1", + "mmf-om2", + "mmf-om3", + "mmf-om4", + "mmf-om5", + "smf", + "smf-os1", + "smf-os2", + "aoc", + "power", + "usb", + "", + null + ], + "type": "string", + "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `mrj21-trunk` - MRJ21 Trunk\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `coaxial` - Coaxial\n* `rg-6` - RG-6\n* `rg-8` - RG-8\n* `rg-11` - RG-11\n* `rg-59` - RG-59\n* `rg-62` - RG-62\n* `rg-213` - RG-213\n* `lmr-100` - LMR-100\n* `lmr-200` - LMR-200\n* `lmr-400` - LMR-400\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Single-mode Fiber\n* `smf-os1` - Single-mode Fiber (OS1)\n* `smf-os2` - Single-mode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `power` - Power\n* `usb` - USB", + "x-spec-enum-id": "3d4d8d7ae24f7be8", + "nullable": true + }, + "a_terminations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GenericObjectRequest" + } + }, + "b_terminations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GenericObjectRequest" + } + }, + "status": { + "enum": [ + "connected", + "planned", + "decommissioning" + ], + "type": "string", + "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "80d251a40f3a3144" + }, + "profile": { + "enum": [ + "single-1c1p", + "single-1c2p", + "single-1c4p", + "single-1c6p", + "single-1c8p", + "single-1c12p", + "single-1c16p", + "trunk-2c1p", + "trunk-2c2p", + "trunk-2c4p", + "trunk-2c4p-shuffle", + "trunk-2c6p", + "trunk-2c8p", + "trunk-2c12p", + "trunk-4c1p", + "trunk-4c2p", + "trunk-4c4p", + "trunk-4c4p-shuffle", + "trunk-4c6p", + "trunk-4c8p", + "trunk-8c4p", + "breakout-1c2p-2c1p", + "breakout-1c4p-4c1p", + "breakout-1c6p-6c1p", + "breakout-2c4p-8c1p-shuffle" + ], + "type": "string", + "description": "* `single-1c1p` - 1C1P\n* `single-1c2p` - 1C2P\n* `single-1c4p` - 1C4P\n* `single-1c6p` - 1C6P\n* `single-1c8p` - 1C8P\n* `single-1c12p` - 1C12P\n* `single-1c16p` - 1C16P\n* `trunk-2c1p` - 2C1P trunk\n* `trunk-2c2p` - 2C2P trunk\n* `trunk-2c4p` - 2C4P trunk\n* `trunk-2c4p-shuffle` - 2C4P trunk (shuffle)\n* `trunk-2c6p` - 2C6P trunk\n* `trunk-2c8p` - 2C8P trunk\n* `trunk-2c12p` - 2C12P trunk\n* `trunk-4c1p` - 4C1P trunk\n* `trunk-4c2p` - 4C2P trunk\n* `trunk-4c4p` - 4C4P trunk\n* `trunk-4c4p-shuffle` - 4C4P trunk (shuffle)\n* `trunk-4c6p` - 4C6P trunk\n* `trunk-4c8p` - 4C8P trunk\n* `trunk-8c4p` - 8C4P trunk\n* `breakout-1c2p-2c1p` - 1C2P:2C1P breakout\n* `breakout-1c4p-4c1p` - 1C4P:4C1P breakout\n* `breakout-1c6p-6c1p` - 1C6P:6C1P breakout\n* `breakout-2c4p-8c1p-shuffle` - 2C4P:8C1P breakout (shuffle)", + "x-spec-enum-id": "f566e6df6572f5d0" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "bundle": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefCableBundleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "label": { + "type": "string", + "maxLength": 100 + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "length": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "length_unit": { + "enum": [ + "km", + "m", + "cm", + "mi", + "ft", + "in", + "", + null + ], + "type": "string", + "description": "* `km` - Kilometers\n* `m` - Meters\n* `cm` - Centimeters\n* `mi` - Miles\n* `ft` - Feet\n* `in` - Inches", + "x-spec-enum-id": "6e7645525ba02462", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "BulkCircuitGroupAssignmentRequest": { + "type": "object", + "description": "Base serializer for group assignments under CircuitSerializer.", + "properties": { + "id": { + "type": "integer" + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefCircuitGroupRequest" + } + ] + }, + "member_type": { + "type": "string" + }, + "member_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "priority": { + "enum": [ + "primary", + "secondary", + "tertiary", + "inactive", + "" + ], + "type": "string", + "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive", + "x-spec-enum-id": "0548fc537440bf9d" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + } + }, + "required": [ + "group", + "id", + "member_id", + "member_type" + ] + }, + "BulkCircuitGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkCircuitRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "cid": { + "type": "string", + "minLength": 1, + "title": "Circuit ID", + "description": "Unique circuit ID", + "maxLength": 100 + }, + "provider": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefProviderRequest" + } + ] + }, + "provider_account": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefProviderAccountRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefCircuitTypeRequest" + } + ] + }, + "status": { + "enum": [ + "planned", + "provisioning", + "active", + "offline", + "deprovisioning", + "decommissioned" + ], + "type": "string", + "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned", + "x-spec-enum-id": "0a239d878b6666a4" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "install_date": { + "type": "string", + "format": "date", + "nullable": true, + "title": "Installed" + }, + "termination_date": { + "type": "string", + "format": "date", + "nullable": true, + "title": "Terminates" + }, + "commit_rate": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "Commit rate (Kbps)", + "description": "Committed rate" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "distance": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "distance_unit": { + "enum": [ + "km", + "m", + "mi", + "ft", + "", + null + ], + "type": "string", + "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet", + "x-spec-enum-id": "b1169a409430c02e", + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "assignments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BriefCircuitGroupAssignmentSerializer_Request" + } + } + }, + "required": [ + "cid", + "id", + "provider", + "type" + ] + }, + "BulkCircuitTerminationRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "circuit": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefCircuitRequest" + } + ] + }, + "term_side": { + "enum": [ + "A", + "Z" + ], + "type": "string", + "description": "* `A` - A\n* `Z` - Z", + "x-spec-enum-id": "95b8fcc737f355d0", + "title": "Termination side" + }, + "termination_type": { + "type": "string", + "nullable": true + }, + "termination_id": { + "type": "integer", + "nullable": true + }, + "port_speed": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "Port speed (Kbps)", + "description": "Physical circuit speed" + }, + "upstream_speed": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "Upstream speed (Kbps)", + "description": "Upstream speed, if different from port speed" + }, + "xconnect_id": { + "type": "string", + "title": "Cross-connect ID", + "description": "ID of the local cross-connect", + "maxLength": 50 + }, + "pp_info": { + "type": "string", + "title": "Patch panel/port(s)", + "description": "Patch panel ID and port number(s)", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "circuit", + "id", + "term_side" + ] + }, + "BulkCircuitTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkClusterGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkClusterRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefClusterTypeRequest" + } + ] + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefClusterGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "planned", + "staging", + "active", + "decommissioning", + "offline" + ], + "type": "string", + "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `offline` - Offline", + "x-spec-enum-id": "65a25166053759eb" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "scope_type": { + "type": "string", + "nullable": true + }, + "scope_id": { + "type": "integer", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "type" + ] + }, + "BulkClusterTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkConfigContextProfileRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "schema": { + "nullable": true, + "description": "A JSON schema specifying the structure of the context data for this profile" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "data_source": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDataSourceRequest" + } + ] + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkConfigContextRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "profile": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigContextProfileRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "is_active": { + "type": "boolean" + }, + "regions": { + "type": "array", + "items": { + "type": "integer" + } + }, + "site_groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "sites": { + "type": "array", + "items": { + "type": "integer" + } + }, + "locations": { + "type": "array", + "items": { + "type": "integer" + } + }, + "device_types": { + "type": "array", + "items": { + "type": "integer" + } + }, + "roles": { + "type": "array", + "items": { + "type": "integer" + } + }, + "platforms": { + "type": "array", + "items": { + "type": "integer" + } + }, + "cluster_types": { + "type": "array", + "items": { + "type": "integer" + } + }, + "cluster_groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "clusters": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tenant_groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tenants": { + "type": "array", + "items": { + "type": "integer" + } + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "data_source": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDataSourceRequest" + } + ] + }, + "data": {} + }, + "required": [ + "data", + "id", + "name" + ] + }, + "BulkConfigTemplateRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "environment_params": { + "nullable": true, + "title": "Environment parameters", + "description": "Any additional parameters to pass when constructing the Jinja environment" + }, + "template_code": { + "type": "string", + "minLength": 1, + "description": "Jinja template code." + }, + "mime_type": { + "type": "string", + "description": "Defaults to text/plain; charset=utf-8", + "maxLength": 50 + }, + "file_name": { + "type": "string", + "description": "Filename to give to the rendered export file", + "maxLength": 200 + }, + "file_extension": { + "type": "string", + "description": "Extension to append to the rendered filename", + "maxLength": 15 + }, + "as_attachment": { + "type": "boolean", + "description": "Download file as attachment" + }, + "debug": { + "type": "boolean", + "description": "Enable verbose error output when rendering this template. Not recommended for production use." + }, + "data_source": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDataSourceRequest" + } + ] + }, + "auto_sync_enabled": { + "type": "boolean", + "description": "Enable automatic synchronization of data when the data file is updated" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + } + }, + "required": [ + "id", + "name", + "template_code" + ] + }, + "BulkConsolePortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other", + "" + ], + "type": "string", + "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "7b8d0e83a4bb5178" + }, + "speed": { + "enum": [ + 1200, + 2400, + 4800, + 9600, + 19200, + 38400, + 57600, + 115200, + null + ], + "type": "integer", + "description": "* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps", + "x-spec-enum-id": "ab6d9635c131a378", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name" + ] + }, + "BulkConsolePortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other", + "" + ], + "type": "string", + "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "7b8d0e83a4bb5178" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkConsoleServerPortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other", + "" + ], + "type": "string", + "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "7b8d0e83a4bb5178" + }, + "speed": { + "enum": [ + 1200, + 2400, + 4800, + 9600, + 19200, + 38400, + 57600, + 115200, + null + ], + "type": "integer", + "description": "* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps", + "x-spec-enum-id": "ab6d9635c131a378", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name" + ] + }, + "BulkConsoleServerPortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other", + "" + ], + "type": "string", + "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "7b8d0e83a4bb5178" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkContactAssignmentRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "contact": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefContactRequest" + } + ] + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefContactRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "priority": { + "enum": [ + "primary", + "secondary", + "tertiary", + "inactive", + "" + ], + "type": "string", + "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive", + "x-spec-enum-id": "0548fc537440bf9d" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "contact", + "id", + "object_id", + "object_type" + ] + }, + "BulkContactGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedContactGroupRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkContactRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "title": { + "type": "string", + "maxLength": 100 + }, + "phone": { + "type": "string", + "maxLength": 50 + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "address": { + "type": "string", + "maxLength": 200 + }, + "link": { + "type": "string", + "format": "uri", + "maxLength": 200 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkContactRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkCustomFieldChoiceSetRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "base_choices": { + "enum": [ + "IATA", + "ISO_3166", + "UN_LOCODE" + ], + "type": "string", + "description": "* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)", + "x-spec-enum-id": "cf0efb5195f85007" + }, + "extra_choices": { + "type": "array", + "items": { + "type": "array", + "items": {}, + "maxItems": 2, + "minItems": 2 + } + }, + "choice_colors": { + "type": "object", + "additionalProperties": { + "enum": [ + "blue", + "indigo", + "purple", + "pink", + "red", + "orange", + "yellow", + "green", + "teal", + "cyan", + "gray", + "black", + "white" + ], + "type": "string", + "description": "* `blue` - Blue\n* `indigo` - Indigo\n* `purple` - Purple\n* `pink` - Pink\n* `red` - Red\n* `orange` - Orange\n* `yellow` - Yellow\n* `green` - Green\n* `teal` - Teal\n* `cyan` - Cyan\n* `gray` - Gray\n* `black` - Black\n* `white` - White", + "x-spec-enum-id": "de0a6a124020dfe0" + } + }, + "order_alphabetically": { + "type": "boolean", + "description": "Choices are automatically ordered alphabetically" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + } + }, + "required": [ + "extra_choices", + "id", + "name" + ] + }, + "BulkCustomFieldRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "enum": [ + "text", + "longtext", + "integer", + "decimal", + "boolean", + "date", + "datetime", + "url", + "json", + "select", + "multiselect", + "object", + "multiobject" + ], + "type": "string", + "description": "* `text` - Text\n* `longtext` - Text (long)\n* `integer` - Integer\n* `decimal` - Decimal\n* `boolean` - Boolean (true/false)\n* `date` - Date\n* `datetime` - Date & time\n* `url` - URL\n* `json` - JSON\n* `select` - Selection\n* `multiselect` - Multiple selection\n* `object` - Object\n* `multiobject` - Multiple objects", + "x-spec-enum-id": "47c52a3d983e924c" + }, + "related_object_type": { + "type": "string", + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Internal field name", + "pattern": "^[a-z0-9_]+$", + "maxLength": 50 + }, + "label": { + "type": "string", + "description": "Name of the field as displayed to users (if not provided, 'the field's name will be used)", + "maxLength": 50 + }, + "group_name": { + "type": "string", + "description": "Custom fields within the same group will be displayed together", + "maxLength": 50 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "required": { + "type": "boolean", + "description": "This field is required when creating new objects or editing an existing object." + }, + "unique": { + "type": "boolean", + "title": "Must be unique", + "description": "The value of this field must be unique for the assigned object" + }, + "search_weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "description": "Weighting for search. Lower values are considered more important. Fields with a search weight of zero will be ignored." + }, + "filter_logic": { + "enum": [ + "disabled", + "loose", + "exact" + ], + "type": "string", + "description": "* `disabled` - Disabled\n* `loose` - Loose\n* `exact` - Exact", + "x-spec-enum-id": "d168820c798ae45a" + }, + "ui_visible": { + "enum": [ + "always", + "if-set", + "hidden" + ], + "type": "string", + "description": "* `always` - Always\n* `if-set` - If set\n* `hidden` - Hidden", + "x-spec-enum-id": "f32800c399b927b6" + }, + "ui_editable": { + "enum": [ + "yes", + "no", + "hidden" + ], + "type": "string", + "description": "* `yes` - Yes\n* `no` - No\n* `hidden` - Hidden", + "x-spec-enum-id": "336f52760e62022f" + }, + "is_cloneable": { + "type": "boolean", + "description": "Replicate this value when cloning objects" + }, + "default": { + "nullable": true, + "description": "Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. \"Foo\")." + }, + "related_object_filter": { + "nullable": true, + "description": "Filter the object selection choices using a query_params dict (must be a JSON value).Encapsulate strings with double quotes (e.g. \"Foo\")." + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "title": "Display weight", + "description": "Fields with higher weights appear lower in a form." + }, + "validation_minimum": { + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true, + "title": "Minimum value", + "description": "Minimum allowed value (for numeric fields)" + }, + "validation_maximum": { + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true, + "title": "Maximum value", + "description": "Maximum allowed value (for numeric fields)" + }, + "validation_regex": { + "type": "string", + "description": "Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. For example, ^[A-Z]{3}$ will limit values to exactly three uppercase letters.", + "maxLength": 500 + }, + "validation_schema": { + "nullable": true, + "description": "A JSON schema definition for validating the custom field value" + }, + "choice_set": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefCustomFieldChoiceSetRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "object_types", + "type" + ] + }, + "BulkCustomLinkRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "enabled": { + "type": "boolean" + }, + "link_text": { + "type": "string", + "minLength": 1, + "description": "Jinja2 template code for link text" + }, + "link_url": { + "type": "string", + "minLength": 1, + "description": "Jinja2 template code for link URL" + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "group_name": { + "type": "string", + "description": "Links with the same group will appear as a dropdown menu", + "maxLength": 50 + }, + "button_class": { + "enum": [ + "default", + "blue", + "indigo", + "purple", + "pink", + "red", + "orange", + "yellow", + "green", + "teal", + "cyan", + "gray", + "black", + "white", + "ghost-dark" + ], + "type": "string", + "x-spec-enum-id": "5e54b3bd086685ce", + "description": "The class of the first link in a group will be used for the dropdown button\n\n* `default` - Default\n* `blue` - Blue\n* `indigo` - Indigo\n* `purple` - Purple\n* `pink` - Pink\n* `red` - Red\n* `orange` - Orange\n* `yellow` - Yellow\n* `green` - Green\n* `teal` - Teal\n* `cyan` - Cyan\n* `gray` - Gray\n* `black` - Black\n* `white` - White\n* `ghost-dark` - Link" + }, + "new_window": { + "type": "boolean", + "description": "Force link to open in a new window" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + } + }, + "required": [ + "id", + "link_text", + "link_url", + "name", + "object_types" + ] + }, + "BulkDataSourceRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "type": { + "enum": [ + null, + "local", + "git", + "amazon-s3" + ], + "description": "* `None` - ---------\n* `local` - Local\n* `git` - Git\n* `amazon-s3` - Amazon S3", + "x-spec-enum-id": "562b613a749b34b0" + }, + "source_url": { + "type": "string", + "minLength": 1, + "title": "URL", + "maxLength": 200 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "sync_interval": { + "enum": [ + 1, + 60, + 720, + 1440, + 10080, + 43200, + null + ], + "type": "integer", + "description": "* `1` - Minutely\n* `60` - Hourly\n* `720` - 12 hours\n* `1440` - Daily\n* `10080` - Weekly\n* `43200` - 30 days", + "x-spec-enum-id": "2e9f2567ecd93fbe", + "nullable": true, + "minimum": 0, + "maximum": 32767 + }, + "parameters": { + "nullable": true + }, + "ignore_rules": { + "type": "string", + "description": "Patterns (one per line) matching files or paths to ignore when syncing" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "source_url", + "type" + ] + }, + "BulkDeviceBayRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "installed_device": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name" + ] + }, + "BulkDeviceBayTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "device_type", + "id", + "name" + ] + }, + "BulkDeviceRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "minLength": 1, + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "vm_role": { + "type": "boolean", + "description": "Virtual machines may be assigned to this role" + }, + "config_template": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedDeviceRoleRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkDeviceTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ] + }, + "default_platform": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPlatformRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "model": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "part_number": { + "type": "string", + "description": "Discrete part number (optional)", + "maxLength": 50 + }, + "u_height": { + "type": "number", + "format": "double", + "maximum": 1000, + "minimum": 0.0, + "exclusiveMaximum": true, + "default": 1.0, + "title": "Position (U)" + }, + "exclude_from_utilization": { + "type": "boolean", + "description": "Devices of this type are excluded when calculating rack utilization." + }, + "is_full_depth": { + "type": "boolean", + "description": "Device consumes both front and rear rack faces." + }, + "subdevice_role": { + "enum": [ + "parent", + "child", + "", + null + ], + "type": "string", + "description": "* `parent` - Parent\n* `child` - Child", + "x-spec-enum-id": "65a61d5e1deb4a24", + "nullable": true + }, + "airflow": { + "enum": [ + "front-to-rear", + "rear-to-front", + "left-to-right", + "right-to-left", + "side-to-rear", + "rear-to-side", + "bottom-to-top", + "top-to-bottom", + "passive", + "mixed", + "", + null + ], + "type": "string", + "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed", + "x-spec-enum-id": "11cb3d363b41ba9e", + "nullable": true + }, + "weight": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "weight_unit": { + "enum": [ + "kg", + "g", + "lb", + "oz", + "", + null + ], + "type": "string", + "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces", + "x-spec-enum-id": "2235ce3f404afbc0", + "nullable": true + }, + "front_image": { + "type": "string", + "format": "binary", + "nullable": true + }, + "rear_image": { + "type": "string", + "format": "binary", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "manufacturer", + "model", + "slug" + ] + }, + "BulkDeviceWithConfigContextRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "nullable": true, + "maxLength": 64 + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ] + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRoleRequest" + } + ] + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "platform": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPlatformRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "serial": { + "type": "string", + "title": "Serial number", + "description": "Chassis serial number, assigned by the manufacturer", + "maxLength": 50 + }, + "asset_tag": { + "type": "string", + "nullable": true, + "description": "A unique tag used to identify this device", + "maxLength": 50 + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ] + }, + "location": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefLocationRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "rack": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "position": { + "type": "number", + "format": "double", + "maximum": 1000, + "minimum": 0.5, + "exclusiveMaximum": true, + "nullable": true, + "title": "Position (U)" + }, + "face": { + "enum": [ + "front", + "rear", + "" + ], + "type": "string", + "description": "* `front` - Front\n* `rear` - Rear", + "x-spec-enum-id": "d2fb9b3f75158b83" + }, + "latitude": { + "type": "number", + "format": "double", + "maximum": 90.0, + "minimum": -90.0, + "nullable": true, + "description": "GPS coordinate in decimal format (xx.yyyyyy)" + }, + "longitude": { + "type": "number", + "format": "double", + "maximum": 180.0, + "minimum": -180.0, + "nullable": true, + "description": "GPS coordinate in decimal format (xx.yyyyyy)" + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "staged", + "failed", + "inventory", + "decommissioning" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `inventory` - Inventory\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "65feb4244cc9110c" + }, + "airflow": { + "enum": [ + "front-to-rear", + "rear-to-front", + "left-to-right", + "right-to-left", + "side-to-rear", + "rear-to-side", + "bottom-to-top", + "top-to-bottom", + "passive", + "mixed", + "" + ], + "type": "string", + "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed", + "x-spec-enum-id": "11cb3d363b41ba9e" + }, + "primary_ip4": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip6": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "oob_ip": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "cluster": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefClusterRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "virtual_chassis": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVirtualChassisRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vc_position": { + "type": "integer", + "maximum": 255, + "minimum": 0, + "nullable": true + }, + "vc_priority": { + "type": "integer", + "maximum": 255, + "minimum": 0, + "nullable": true, + "description": "Virtual chassis master election priority" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "config_template": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "local_context_data": { + "nullable": true, + "description": "Local config context data takes precedence over source contexts in the final rendered config context" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device_type", + "id", + "role", + "site" + ] + }, + "BulkEventRuleRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "enabled": { + "type": "boolean" + }, + "event_types": { + "type": "array", + "items": { + "enum": [ + "object_created", + "object_updated", + "object_deleted", + "job_started", + "job_completed", + "job_failed", + "job_errored" + ], + "type": "string", + "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored", + "x-spec-enum-id": "01e557313a5c7bd2" + }, + "description": "The types of event which will trigger this rule." + }, + "conditions": { + "nullable": true, + "description": "A set of conditions which determine whether the event will be generated." + }, + "action_type": { + "enum": [ + "webhook", + "script", + "notification" + ], + "type": "string", + "description": "* `webhook` - Webhook\n* `script` - Script\n* `notification` - Notification", + "x-spec-enum-id": "287901b937995956" + }, + "action_object_type": { + "type": "string" + }, + "action_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + } + }, + "required": [ + "action_object_type", + "action_type", + "event_types", + "id", + "name", + "object_types" + ] + }, + "BulkExportTemplateRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "environment_params": { + "nullable": true, + "title": "Environment parameters", + "description": "Any additional parameters to pass when constructing the Jinja environment" + }, + "template_code": { + "type": "string", + "minLength": 1, + "description": "Jinja template code." + }, + "mime_type": { + "type": "string", + "description": "Defaults to text/plain; charset=utf-8", + "maxLength": 50 + }, + "file_name": { + "type": "string", + "description": "Filename to give to the rendered export file", + "maxLength": 200 + }, + "file_extension": { + "type": "string", + "description": "Extension to append to the rendered filename", + "maxLength": 15 + }, + "as_attachment": { + "type": "boolean", + "description": "Download file as attachment" + }, + "data_source": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDataSourceRequest" + } + ] + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + } + }, + "required": [ + "id", + "name", + "object_types", + "template_code" + ] + }, + "BulkFHRPGroupAssignmentRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefFHRPGroupRequest" + } + ] + }, + "interface_type": { + "type": "string" + }, + "interface_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "priority": { + "type": "integer", + "maximum": 255, + "minimum": 0 + } + }, + "required": [ + "group", + "id", + "interface_id", + "interface_type", + "priority" + ] + }, + "BulkFHRPGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "maxLength": 100 + }, + "protocol": { + "enum": [ + "vrrp2", + "vrrp3", + "carp", + "clusterxl", + "hsrp", + "glbp", + "other" + ], + "type": "string", + "description": "* `vrrp2` - VRRPv2\n* `vrrp3` - VRRPv3\n* `carp` - CARP\n* `clusterxl` - ClusterXL\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `other` - Other", + "x-spec-enum-id": "98de93c9f65d1c65" + }, + "group_id": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "auth_type": { + "enum": [ + "plaintext", + "md5", + "", + null + ], + "type": "string", + "description": "* `plaintext` - Plaintext\n* `md5` - MD5", + "x-spec-enum-id": "565396e386e1542a", + "nullable": true, + "title": "Authentication type" + }, + "auth_key": { + "type": "string", + "title": "Authentication key", + "maxLength": 255 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "group_id", + "id", + "protocol" + ] + }, + "BulkFrontPortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "fc-pc", + "fc-upc", + "fc-apc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ], + "type": "string", + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "positions": { + "type": "integer", + "maximum": 1024, + "minimum": 1 + }, + "rear_ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FrontPortMappingRequest" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name", + "type" + ] + }, + "BulkFrontPortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "fc-pc", + "fc-upc", + "fc-apc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ], + "type": "string", + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "positions": { + "type": "integer", + "maximum": 1024, + "minimum": 1 + }, + "rear_ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FrontPortTemplateMappingRequest" + } + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "name", + "type" + ] + }, + "BulkGroupRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "permissions": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkIKEPolicyRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "version": { + "enum": [ + 1, + 2 + ], + "type": "integer", + "description": "* `1` - IKEv1\n* `2` - IKEv2", + "x-spec-enum-id": "00872b77916a1fde" + }, + "mode": { + "enum": [ + "aggressive", + "main" + ], + "type": "string", + "description": "* `aggressive` - Aggressive\n* `main` - Main", + "x-spec-enum-id": "64c1be7bdb2548ca" + }, + "proposals": { + "type": "array", + "items": { + "type": "integer" + } + }, + "preshared_key": { + "type": "string", + "title": "Pre-shared key" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "version" + ] + }, + "BulkIKEProposalRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "authentication_method": { + "enum": [ + "preshared-keys", + "certificates", + "rsa-signatures", + "dsa-signatures" + ], + "type": "string", + "description": "* `preshared-keys` - Pre-shared keys\n* `certificates` - Certificates\n* `rsa-signatures` - RSA signatures\n* `dsa-signatures` - DSA signatures", + "x-spec-enum-id": "a21158c52d0c455a" + }, + "encryption_algorithm": { + "enum": [ + "aes-128-cbc", + "aes-128-gcm", + "aes-192-cbc", + "aes-192-gcm", + "aes-256-cbc", + "aes-256-gcm", + "3des-cbc", + "des-cbc" + ], + "type": "string", + "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES", + "x-spec-enum-id": "ae3dabd7b2b3cba2" + }, + "authentication_algorithm": { + "enum": [ + "hmac-sha1", + "hmac-sha256", + "hmac-sha384", + "hmac-sha512", + "hmac-md5" + ], + "type": "string", + "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC", + "x-spec-enum-id": "0a7ca69695b483a7" + }, + "group": { + "enum": [ + 1, + 2, + 5, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34 + ], + "type": "integer", + "description": "* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34", + "x-spec-enum-id": "dbef43be795462a8" + }, + "sa_lifetime": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "description": "Security association lifetime (in seconds)" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "authentication_method", + "encryption_algorithm", + "group", + "id", + "name" + ] + }, + "BulkIPAddressRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "address": { + "type": "string", + "minLength": 1 + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "reserved", + "deprecated", + "dhcp", + "slaac" + ], + "type": "string", + "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated\n* `dhcp` - DHCP\n* `slaac` - SLAAC", + "x-spec-enum-id": "c421c4c4a0fa7a2a" + }, + "role": { + "enum": [ + "loopback", + "secondary", + "anycast", + "vip", + "vrrp", + "hsrp", + "glbp", + "carp", + "" + ], + "type": "string", + "description": "* `loopback` - Loopback\n* `secondary` - Secondary\n* `anycast` - Anycast\n* `vip` - VIP\n* `vrrp` - VRRP\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `carp` - CARP", + "x-spec-enum-id": "53dca4cddd7b344a" + }, + "assigned_object_type": { + "type": "string", + "nullable": true + }, + "assigned_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "nat_inside": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedIPAddressRequest" + } + ], + "nullable": true + }, + "dns_name": { + "type": "string", + "description": "Hostname or FQDN (not case-sensitive)", + "pattern": "^([0-9A-Za-z_-]+|\\*)(\\.[0-9A-Za-z_-]+)*\\.?$", + "maxLength": 255 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "address", + "id" + ] + }, + "BulkIPRangeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "start_address": { + "type": "string", + "minLength": 1 + }, + "end_address": { + "type": "string", + "minLength": 1 + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "reserved", + "deprecated" + ], + "type": "string", + "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated", + "x-spec-enum-id": "ca933c38b935e547" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "mark_populated": { + "type": "boolean", + "description": "Prevent the creation of IP addresses within this range" + }, + "mark_utilized": { + "type": "boolean", + "description": "Report space as fully utilized" + } + }, + "required": [ + "end_address", + "id", + "start_address" + ] + }, + "BulkIPSecPolicyRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "proposals": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pfs_group": { + "enum": [ + 1, + 2, + 5, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34 + ], + "type": "integer", + "description": "* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34", + "x-spec-enum-id": "dbef43be795462a8" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkIPSecProfileRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mode": { + "enum": [ + "esp", + "ah" + ], + "type": "string", + "description": "* `esp` - ESP\n* `ah` - AH", + "x-spec-enum-id": "87ac6ada0da14ccf" + }, + "ike_policy": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefIKEPolicyRequest" + } + ] + }, + "ipsec_policy": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefIPSecPolicyRequest" + } + ] + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "ike_policy", + "ipsec_policy", + "mode", + "name" + ] + }, + "BulkIPSecProposalRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "encryption_algorithm": { + "enum": [ + "aes-128-cbc", + "aes-128-gcm", + "aes-192-cbc", + "aes-192-gcm", + "aes-256-cbc", + "aes-256-gcm", + "3des-cbc", + "des-cbc" + ], + "type": "string", + "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES", + "x-spec-enum-id": "ae3dabd7b2b3cba2" + }, + "authentication_algorithm": { + "enum": [ + "hmac-sha1", + "hmac-sha256", + "hmac-sha384", + "hmac-sha512", + "hmac-md5" + ], + "type": "string", + "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC", + "x-spec-enum-id": "0a7ca69695b483a7" + }, + "sa_lifetime_seconds": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "SA lifetime (seconds)", + "description": "Security association lifetime (seconds)" + }, + "sa_lifetime_data": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "SA lifetime (KB)", + "description": "Security association lifetime (in kilobytes)" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkImageAttachmentRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "name": { + "type": "string", + "maxLength": 50 + }, + "image": { + "type": "string", + "format": "binary" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "image", + "object_id", + "object_type" + ] + }, + "BulkInterfaceRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "vdcs": { + "type": "array", + "items": { + "type": "integer" + } + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "virtual", + "bridge", + "lag", + "100base-fx", + "100base-lfx", + "100base-tx", + "100base-t1", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cwdm", + "1000base-cx", + "1000base-dwdm", + "1000base-ex", + "1000base-lsx", + "1000base-lx", + "1000base-lx10", + "1000base-sx", + "1000base-t", + "1000base-tx", + "1000base-zx", + "2.5gbase-t", + "5gbase-t", + "10gbase-br-d", + "10gbase-br-u", + "10gbase-cu", + "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "40gbase-sr4-bd", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-cwdm4", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", + "1.6tbase-cr8", + "1.6tbase-dr8", + "1.6tbase-dr8-2", + "100base-x-sfp", + "1000base-x-gbic", + "1000base-x-sfp", + "2.5gbase-x-sfp", + "10gbase-x-sfpp", + "10gbase-x-xenpak", + "10gbase-x-xfp", + "10gbase-x-x2", + "25gbase-x-sfp28", + "40gbase-x-qsfpp", + "50gbase-x-sfp28", + "50gbase-x-sfp56", + "100gbase-x-cfp", + "100gbase-x-cfp2", + "100gbase-x-cfp4", + "100gbase-x-cxp", + "100gbase-x-cpak", + "100gbase-x-dsfp", + "100gbase-x-qsfp28", + "100gbase-x-qsfpdd", + "100gbase-x-sfpdd", + "200gbase-x-cfp2", + "200gbase-x-qsfp56", + "200gbase-x-qsfpdd", + "400gbase-x-qsfp112", + "400gbase-x-qsfpdd", + "400gbase-x-cdfp", + "400gbase-x-cfp2", + "400gbase-x-cfp8", + "400gbase-x-osfp", + "400gbase-x-osfp-rhs", + "800gbase-x-osfp", + "800gbase-x-qsfpdd", + "1.6tbase-x-osfp1600", + "1.6tbase-x-osfp1600-rhs", + "1.6tbase-x-qsfpdd1600", + "1000base-kx", + "2.5gbase-kx", + "5gbase-kr", + "10gbase-kr", + "10gbase-kx4", + "25gbase-kr", + "40gbase-kr4", + "50gbase-kr", + "100gbase-kp4", + "100gbase-kr2", + "100gbase-kr4", + "1.6tbase-kr8", + "ieee802.11a", + "ieee802.11g", + "ieee802.11n", + "ieee802.11ac", + "ieee802.11ad", + "ieee802.11ax", + "ieee802.11ay", + "ieee802.11be", + "ieee802.15.1", + "ieee802.15.4", + "other-wireless", + "gsm", + "cdma", + "lte", + "4g", + "5g", + "sonet-oc3", + "sonet-oc12", + "sonet-oc48", + "sonet-oc192", + "sonet-oc768", + "sonet-oc1920", + "sonet-oc3840", + "1gfc-sfp", + "2gfc-sfp", + "4gfc-sfp", + "8gfc-sfpp", + "16gfc-sfpp", + "32gfc-sfp28", + "32gfc-sfpp", + "64gfc-qsfpp", + "64gfc-sfpdd", + "64gfc-sfpp", + "128gfc-qsfp28", + "infiniband-sdr", + "infiniband-ddr", + "infiniband-qdr", + "infiniband-fdr10", + "infiniband-fdr", + "infiniband-edr", + "infiniband-hdr", + "infiniband-ndr", + "infiniband-xdr", + "t1", + "e1", + "t3", + "e3", + "xdsl", + "docsis", + "moca", + "bpon", + "epon", + "10g-epon", + "gpon", + "xg-pon", + "xgs-pon", + "ng-pon2", + "25g-pon", + "50g-pon", + "cisco-stackwise", + "cisco-stackwise-plus", + "cisco-flexstack", + "cisco-flexstack-plus", + "cisco-stackwise-80", + "cisco-stackwise-160", + "cisco-stackwise-320", + "cisco-stackwise-480", + "cisco-stackwise-1t", + "juniper-vcp", + "extreme-summitstack", + "extreme-summitstack-128", + "extreme-summitstack-256", + "extreme-summitstack-512", + "other" + ], + "type": "string", + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cu` - 10GBASE-CU (10GE DAC Passive Twinax)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `40gbase-sr4-bd` - 40GBASE-SR4 (40GE BiDi)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `1.6tbase-cr8` - 1.6TBASE-CR8 (1.6TE)\n* `1.6tbase-dr8` - 1.6TBASE-DR8 (1.6TE)\n* `1.6tbase-dr8-2` - 1.6TBASE-DR8-2 (1.6TE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `2.5gbase-x-sfp` - SFP (2.5GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1.6tbase-x-osfp1600` - OSFP1600 (1.6TE)\n* `1.6tbase-x-osfp1600-rhs` - OSFP1600-RHS (1.6TE)\n* `1.6tbase-x-qsfpdd1600` - QSFP-DD1600 (1.6TE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `1.6tbase-kr8` - 1.6TBASE-KR8 (1.6TE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "b067eb1f050c6ae9" + }, + "enabled": { + "type": "boolean" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedInterfaceRequest" + } + ], + "nullable": true + }, + "bridge": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedInterfaceRequest" + } + ], + "nullable": true + }, + "lag": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedInterfaceRequest" + } + ], + "nullable": true + }, + "mtu": { + "type": "integer", + "maximum": 65536, + "minimum": 1, + "nullable": true + }, + "primary_mac_address": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefMACAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "speed": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true, + "title": "Speed (Kbps)" + }, + "duplex": { + "enum": [ + "half", + "full", + "auto", + "", + null + ], + "type": "string", + "description": "* `half` - Half\n* `full` - Full\n* `auto` - Auto", + "x-spec-enum-id": "368458a2b67c916b", + "nullable": true + }, + "wwn": { + "type": "string", + "nullable": true + }, + "mgmt_only": { + "type": "boolean", + "title": "Management only", + "description": "This interface is used only for out-of-band management" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mode": { + "enum": [ + "access", + "tagged", + "tagged-all", + "q-in-q", + "" + ], + "type": "string", + "description": "* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)", + "x-spec-enum-id": "84129b71b974ebe5" + }, + "rf_role": { + "enum": [ + "ap", + "station", + "" + ], + "type": "string", + "description": "* `ap` - Access point\n* `station` - Station", + "x-spec-enum-id": "d2772dbea88b0fb1" + }, + "rf_channel": { + "enum": [ + "2.4g-1-2412-22", + "2.4g-2-2417-22", + "2.4g-3-2422-22", + "2.4g-4-2427-22", + "2.4g-5-2432-22", + "2.4g-6-2437-22", + "2.4g-7-2442-22", + "2.4g-8-2447-22", + "2.4g-9-2452-22", + "2.4g-10-2457-22", + "2.4g-11-2462-22", + "2.4g-12-2467-22", + "2.4g-13-2472-22", + "5g-32-5160-20", + "5g-34-5170-40", + "5g-36-5180-20", + "5g-38-5190-40", + "5g-40-5200-20", + "5g-42-5210-80", + "5g-44-5220-20", + "5g-46-5230-40", + "5g-48-5240-20", + "5g-50-5250-160", + "5g-52-5260-20", + "5g-54-5270-40", + "5g-56-5280-20", + "5g-58-5290-80", + "5g-60-5300-20", + "5g-62-5310-40", + "5g-64-5320-20", + "5g-100-5500-20", + "5g-102-5510-40", + "5g-104-5520-20", + "5g-106-5530-80", + "5g-108-5540-20", + "5g-110-5550-40", + "5g-112-5560-20", + "5g-114-5570-160", + "5g-116-5580-20", + "5g-118-5590-40", + "5g-120-5600-20", + "5g-122-5610-80", + "5g-124-5620-20", + "5g-126-5630-40", + "5g-128-5640-20", + "5g-132-5660-20", + "5g-134-5670-40", + "5g-136-5680-20", + "5g-138-5690-80", + "5g-140-5700-20", + "5g-142-5710-40", + "5g-144-5720-20", + "5g-149-5745-20", + "5g-151-5755-40", + "5g-153-5765-20", + "5g-155-5775-80", + "5g-157-5785-20", + "5g-159-5795-40", + "5g-161-5805-20", + "5g-163-5815-160", + "5g-165-5825-20", + "5g-167-5835-40", + "5g-169-5845-20", + "5g-171-5855-80", + "5g-173-5865-20", + "5g-175-5875-40", + "5g-177-5885-20", + "6g-1-5955-20", + "6g-3-5965-40", + "6g-5-5975-20", + "6g-7-5985-80", + "6g-9-5995-20", + "6g-11-6005-40", + "6g-13-6015-20", + "6g-15-6025-160", + "6g-17-6035-20", + "6g-19-6045-40", + "6g-21-6055-20", + "6g-23-6065-80", + "6g-25-6075-20", + "6g-27-6085-40", + "6g-29-6095-20", + "6g-31-6105-320", + "6g-33-6115-20", + "6g-35-6125-40", + "6g-37-6135-20", + "6g-39-6145-80", + "6g-41-6155-20", + "6g-43-6165-40", + "6g-45-6175-20", + "6g-47-6185-160", + "6g-49-6195-20", + "6g-51-6205-40", + "6g-53-6215-20", + "6g-55-6225-80", + "6g-57-6235-20", + "6g-59-6245-40", + "6g-61-6255-20", + "6g-65-6275-20", + "6g-67-6285-40", + "6g-69-6295-20", + "6g-71-6305-80", + "6g-73-6315-20", + "6g-75-6325-40", + "6g-77-6335-20", + "6g-79-6345-160", + "6g-81-6355-20", + "6g-83-6365-40", + "6g-85-6375-20", + "6g-87-6385-80", + "6g-89-6395-20", + "6g-91-6405-40", + "6g-93-6415-20", + "6g-95-6425-320", + "6g-97-6435-20", + "6g-99-6445-40", + "6g-101-6455-20", + "6g-103-6465-80", + "6g-105-6475-20", + "6g-107-6485-40", + "6g-109-6495-20", + "6g-111-6505-160", + "6g-113-6515-20", + "6g-115-6525-40", + "6g-117-6535-20", + "6g-119-6545-80", + "6g-121-6555-20", + "6g-123-6565-40", + "6g-125-6575-20", + "6g-129-6595-20", + "6g-131-6605-40", + "6g-133-6615-20", + "6g-135-6625-80", + "6g-137-6635-20", + "6g-139-6645-40", + "6g-141-6655-20", + "6g-143-6665-160", + "6g-145-6675-20", + "6g-147-6685-40", + "6g-149-6695-20", + "6g-151-6705-80", + "6g-153-6715-20", + "6g-155-6725-40", + "6g-157-6735-20", + "6g-159-6745-320", + "6g-161-6755-20", + "6g-163-6765-40", + "6g-165-6775-20", + "6g-167-6785-80", + "6g-169-6795-20", + "6g-171-6805-40", + "6g-173-6815-20", + "6g-175-6825-160", + "6g-177-6835-20", + "6g-179-6845-40", + "6g-181-6855-20", + "6g-183-6865-80", + "6g-185-6875-20", + "6g-187-6885-40", + "6g-189-6895-20", + "6g-193-6915-20", + "6g-195-6925-40", + "6g-197-6935-20", + "6g-199-6945-80", + "6g-201-6955-20", + "6g-203-6965-40", + "6g-205-6975-20", + "6g-207-6985-160", + "6g-209-6995-20", + "6g-211-7005-40", + "6g-213-7015-20", + "6g-215-7025-80", + "6g-217-7035-20", + "6g-219-7045-40", + "6g-221-7055-20", + "6g-225-7075-20", + "6g-227-7085-40", + "6g-229-7095-20", + "6g-233-7115-20", + "60g-1-58320-2160", + "60g-2-60480-2160", + "60g-3-62640-2160", + "60g-4-64800-2160", + "60g-5-66960-2160", + "60g-6-69120-2160", + "60g-9-59400-4320", + "60g-10-61560-4320", + "60g-11-63720-4320", + "60g-12-65880-4320", + "60g-13-68040-4320", + "60g-17-60480-6480", + "60g-18-62640-6480", + "60g-19-64800-6480", + "60g-20-66960-6480", + "60g-25-61560-6480", + "60g-26-63720-6480", + "60g-27-65880-6480", + "" + ], + "type": "string", + "description": "* `2.4g-1-2412-22` - 1 (2412 MHz)\n* `2.4g-2-2417-22` - 2 (2417 MHz)\n* `2.4g-3-2422-22` - 3 (2422 MHz)\n* `2.4g-4-2427-22` - 4 (2427 MHz)\n* `2.4g-5-2432-22` - 5 (2432 MHz)\n* `2.4g-6-2437-22` - 6 (2437 MHz)\n* `2.4g-7-2442-22` - 7 (2442 MHz)\n* `2.4g-8-2447-22` - 8 (2447 MHz)\n* `2.4g-9-2452-22` - 9 (2452 MHz)\n* `2.4g-10-2457-22` - 10 (2457 MHz)\n* `2.4g-11-2462-22` - 11 (2462 MHz)\n* `2.4g-12-2467-22` - 12 (2467 MHz)\n* `2.4g-13-2472-22` - 13 (2472 MHz)\n* `5g-32-5160-20` - 32 (5160/20 MHz)\n* `5g-34-5170-40` - 34 (5170/40 MHz)\n* `5g-36-5180-20` - 36 (5180/20 MHz)\n* `5g-38-5190-40` - 38 (5190/40 MHz)\n* `5g-40-5200-20` - 40 (5200/20 MHz)\n* `5g-42-5210-80` - 42 (5210/80 MHz)\n* `5g-44-5220-20` - 44 (5220/20 MHz)\n* `5g-46-5230-40` - 46 (5230/40 MHz)\n* `5g-48-5240-20` - 48 (5240/20 MHz)\n* `5g-50-5250-160` - 50 (5250/160 MHz)\n* `5g-52-5260-20` - 52 (5260/20 MHz)\n* `5g-54-5270-40` - 54 (5270/40 MHz)\n* `5g-56-5280-20` - 56 (5280/20 MHz)\n* `5g-58-5290-80` - 58 (5290/80 MHz)\n* `5g-60-5300-20` - 60 (5300/20 MHz)\n* `5g-62-5310-40` - 62 (5310/40 MHz)\n* `5g-64-5320-20` - 64 (5320/20 MHz)\n* `5g-100-5500-20` - 100 (5500/20 MHz)\n* `5g-102-5510-40` - 102 (5510/40 MHz)\n* `5g-104-5520-20` - 104 (5520/20 MHz)\n* `5g-106-5530-80` - 106 (5530/80 MHz)\n* `5g-108-5540-20` - 108 (5540/20 MHz)\n* `5g-110-5550-40` - 110 (5550/40 MHz)\n* `5g-112-5560-20` - 112 (5560/20 MHz)\n* `5g-114-5570-160` - 114 (5570/160 MHz)\n* `5g-116-5580-20` - 116 (5580/20 MHz)\n* `5g-118-5590-40` - 118 (5590/40 MHz)\n* `5g-120-5600-20` - 120 (5600/20 MHz)\n* `5g-122-5610-80` - 122 (5610/80 MHz)\n* `5g-124-5620-20` - 124 (5620/20 MHz)\n* `5g-126-5630-40` - 126 (5630/40 MHz)\n* `5g-128-5640-20` - 128 (5640/20 MHz)\n* `5g-132-5660-20` - 132 (5660/20 MHz)\n* `5g-134-5670-40` - 134 (5670/40 MHz)\n* `5g-136-5680-20` - 136 (5680/20 MHz)\n* `5g-138-5690-80` - 138 (5690/80 MHz)\n* `5g-140-5700-20` - 140 (5700/20 MHz)\n* `5g-142-5710-40` - 142 (5710/40 MHz)\n* `5g-144-5720-20` - 144 (5720/20 MHz)\n* `5g-149-5745-20` - 149 (5745/20 MHz)\n* `5g-151-5755-40` - 151 (5755/40 MHz)\n* `5g-153-5765-20` - 153 (5765/20 MHz)\n* `5g-155-5775-80` - 155 (5775/80 MHz)\n* `5g-157-5785-20` - 157 (5785/20 MHz)\n* `5g-159-5795-40` - 159 (5795/40 MHz)\n* `5g-161-5805-20` - 161 (5805/20 MHz)\n* `5g-163-5815-160` - 163 (5815/160 MHz)\n* `5g-165-5825-20` - 165 (5825/20 MHz)\n* `5g-167-5835-40` - 167 (5835/40 MHz)\n* `5g-169-5845-20` - 169 (5845/20 MHz)\n* `5g-171-5855-80` - 171 (5855/80 MHz)\n* `5g-173-5865-20` - 173 (5865/20 MHz)\n* `5g-175-5875-40` - 175 (5875/40 MHz)\n* `5g-177-5885-20` - 177 (5885/20 MHz)\n* `6g-1-5955-20` - 1 (5955/20 MHz)\n* `6g-3-5965-40` - 3 (5965/40 MHz)\n* `6g-5-5975-20` - 5 (5975/20 MHz)\n* `6g-7-5985-80` - 7 (5985/80 MHz)\n* `6g-9-5995-20` - 9 (5995/20 MHz)\n* `6g-11-6005-40` - 11 (6005/40 MHz)\n* `6g-13-6015-20` - 13 (6015/20 MHz)\n* `6g-15-6025-160` - 15 (6025/160 MHz)\n* `6g-17-6035-20` - 17 (6035/20 MHz)\n* `6g-19-6045-40` - 19 (6045/40 MHz)\n* `6g-21-6055-20` - 21 (6055/20 MHz)\n* `6g-23-6065-80` - 23 (6065/80 MHz)\n* `6g-25-6075-20` - 25 (6075/20 MHz)\n* `6g-27-6085-40` - 27 (6085/40 MHz)\n* `6g-29-6095-20` - 29 (6095/20 MHz)\n* `6g-31-6105-320` - 31 (6105/320 MHz)\n* `6g-33-6115-20` - 33 (6115/20 MHz)\n* `6g-35-6125-40` - 35 (6125/40 MHz)\n* `6g-37-6135-20` - 37 (6135/20 MHz)\n* `6g-39-6145-80` - 39 (6145/80 MHz)\n* `6g-41-6155-20` - 41 (6155/20 MHz)\n* `6g-43-6165-40` - 43 (6165/40 MHz)\n* `6g-45-6175-20` - 45 (6175/20 MHz)\n* `6g-47-6185-160` - 47 (6185/160 MHz)\n* `6g-49-6195-20` - 49 (6195/20 MHz)\n* `6g-51-6205-40` - 51 (6205/40 MHz)\n* `6g-53-6215-20` - 53 (6215/20 MHz)\n* `6g-55-6225-80` - 55 (6225/80 MHz)\n* `6g-57-6235-20` - 57 (6235/20 MHz)\n* `6g-59-6245-40` - 59 (6245/40 MHz)\n* `6g-61-6255-20` - 61 (6255/20 MHz)\n* `6g-65-6275-20` - 65 (6275/20 MHz)\n* `6g-67-6285-40` - 67 (6285/40 MHz)\n* `6g-69-6295-20` - 69 (6295/20 MHz)\n* `6g-71-6305-80` - 71 (6305/80 MHz)\n* `6g-73-6315-20` - 73 (6315/20 MHz)\n* `6g-75-6325-40` - 75 (6325/40 MHz)\n* `6g-77-6335-20` - 77 (6335/20 MHz)\n* `6g-79-6345-160` - 79 (6345/160 MHz)\n* `6g-81-6355-20` - 81 (6355/20 MHz)\n* `6g-83-6365-40` - 83 (6365/40 MHz)\n* `6g-85-6375-20` - 85 (6375/20 MHz)\n* `6g-87-6385-80` - 87 (6385/80 MHz)\n* `6g-89-6395-20` - 89 (6395/20 MHz)\n* `6g-91-6405-40` - 91 (6405/40 MHz)\n* `6g-93-6415-20` - 93 (6415/20 MHz)\n* `6g-95-6425-320` - 95 (6425/320 MHz)\n* `6g-97-6435-20` - 97 (6435/20 MHz)\n* `6g-99-6445-40` - 99 (6445/40 MHz)\n* `6g-101-6455-20` - 101 (6455/20 MHz)\n* `6g-103-6465-80` - 103 (6465/80 MHz)\n* `6g-105-6475-20` - 105 (6475/20 MHz)\n* `6g-107-6485-40` - 107 (6485/40 MHz)\n* `6g-109-6495-20` - 109 (6495/20 MHz)\n* `6g-111-6505-160` - 111 (6505/160 MHz)\n* `6g-113-6515-20` - 113 (6515/20 MHz)\n* `6g-115-6525-40` - 115 (6525/40 MHz)\n* `6g-117-6535-20` - 117 (6535/20 MHz)\n* `6g-119-6545-80` - 119 (6545/80 MHz)\n* `6g-121-6555-20` - 121 (6555/20 MHz)\n* `6g-123-6565-40` - 123 (6565/40 MHz)\n* `6g-125-6575-20` - 125 (6575/20 MHz)\n* `6g-129-6595-20` - 129 (6595/20 MHz)\n* `6g-131-6605-40` - 131 (6605/40 MHz)\n* `6g-133-6615-20` - 133 (6615/20 MHz)\n* `6g-135-6625-80` - 135 (6625/80 MHz)\n* `6g-137-6635-20` - 137 (6635/20 MHz)\n* `6g-139-6645-40` - 139 (6645/40 MHz)\n* `6g-141-6655-20` - 141 (6655/20 MHz)\n* `6g-143-6665-160` - 143 (6665/160 MHz)\n* `6g-145-6675-20` - 145 (6675/20 MHz)\n* `6g-147-6685-40` - 147 (6685/40 MHz)\n* `6g-149-6695-20` - 149 (6695/20 MHz)\n* `6g-151-6705-80` - 151 (6705/80 MHz)\n* `6g-153-6715-20` - 153 (6715/20 MHz)\n* `6g-155-6725-40` - 155 (6725/40 MHz)\n* `6g-157-6735-20` - 157 (6735/20 MHz)\n* `6g-159-6745-320` - 159 (6745/320 MHz)\n* `6g-161-6755-20` - 161 (6755/20 MHz)\n* `6g-163-6765-40` - 163 (6765/40 MHz)\n* `6g-165-6775-20` - 165 (6775/20 MHz)\n* `6g-167-6785-80` - 167 (6785/80 MHz)\n* `6g-169-6795-20` - 169 (6795/20 MHz)\n* `6g-171-6805-40` - 171 (6805/40 MHz)\n* `6g-173-6815-20` - 173 (6815/20 MHz)\n* `6g-175-6825-160` - 175 (6825/160 MHz)\n* `6g-177-6835-20` - 177 (6835/20 MHz)\n* `6g-179-6845-40` - 179 (6845/40 MHz)\n* `6g-181-6855-20` - 181 (6855/20 MHz)\n* `6g-183-6865-80` - 183 (6865/80 MHz)\n* `6g-185-6875-20` - 185 (6875/20 MHz)\n* `6g-187-6885-40` - 187 (6885/40 MHz)\n* `6g-189-6895-20` - 189 (6895/20 MHz)\n* `6g-193-6915-20` - 193 (6915/20 MHz)\n* `6g-195-6925-40` - 195 (6925/40 MHz)\n* `6g-197-6935-20` - 197 (6935/20 MHz)\n* `6g-199-6945-80` - 199 (6945/80 MHz)\n* `6g-201-6955-20` - 201 (6955/20 MHz)\n* `6g-203-6965-40` - 203 (6965/40 MHz)\n* `6g-205-6975-20` - 205 (6975/20 MHz)\n* `6g-207-6985-160` - 207 (6985/160 MHz)\n* `6g-209-6995-20` - 209 (6995/20 MHz)\n* `6g-211-7005-40` - 211 (7005/40 MHz)\n* `6g-213-7015-20` - 213 (7015/20 MHz)\n* `6g-215-7025-80` - 215 (7025/80 MHz)\n* `6g-217-7035-20` - 217 (7035/20 MHz)\n* `6g-219-7045-40` - 219 (7045/40 MHz)\n* `6g-221-7055-20` - 221 (7055/20 MHz)\n* `6g-225-7075-20` - 225 (7075/20 MHz)\n* `6g-227-7085-40` - 227 (7085/40 MHz)\n* `6g-229-7095-20` - 229 (7095/20 MHz)\n* `6g-233-7115-20` - 233 (7115/20 MHz)\n* `60g-1-58320-2160` - 1 (58.32/2.16 GHz)\n* `60g-2-60480-2160` - 2 (60.48/2.16 GHz)\n* `60g-3-62640-2160` - 3 (62.64/2.16 GHz)\n* `60g-4-64800-2160` - 4 (64.80/2.16 GHz)\n* `60g-5-66960-2160` - 5 (66.96/2.16 GHz)\n* `60g-6-69120-2160` - 6 (69.12/2.16 GHz)\n* `60g-9-59400-4320` - 9 (59.40/4.32 GHz)\n* `60g-10-61560-4320` - 10 (61.56/4.32 GHz)\n* `60g-11-63720-4320` - 11 (63.72/4.32 GHz)\n* `60g-12-65880-4320` - 12 (65.88/4.32 GHz)\n* `60g-13-68040-4320` - 13 (68.04/4.32 GHz)\n* `60g-17-60480-6480` - 17 (60.48/6.48 GHz)\n* `60g-18-62640-6480` - 18 (62.64/6.48 GHz)\n* `60g-19-64800-6480` - 19 (64.80/6.48 GHz)\n* `60g-20-66960-6480` - 20 (66.96/6.48 GHz)\n* `60g-25-61560-6480` - 25 (61.56/8.64 GHz)\n* `60g-26-63720-6480` - 26 (63.72/8.64 GHz)\n* `60g-27-65880-6480` - 27 (65.88/8.64 GHz)", + "x-spec-enum-id": "70cf66176c475063" + }, + "poe_mode": { + "enum": [ + "pd", + "pse", + "" + ], + "type": "string", + "description": "* `pd` - PD\n* `pse` - PSE", + "x-spec-enum-id": "2f2fe6dcdc7772bd" + }, + "poe_type": { + "enum": [ + "type1-ieee802.3af", + "type2-ieee802.3at", + "type3-ieee802.3bt", + "type4-ieee802.3bt", + "passive-24v-2pair", + "passive-24v-4pair", + "passive-48v-2pair", + "passive-48v-4pair", + "" + ], + "type": "string", + "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)", + "x-spec-enum-id": "5473d57885f237ab" + }, + "rf_channel_frequency": { + "type": "number", + "format": "double", + "maximum": 100000, + "minimum": -100000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true, + "title": "Channel frequency (MHz)", + "description": "Populated by selected channel (if set)" + }, + "rf_channel_width": { + "type": "number", + "format": "double", + "maximum": 10000, + "minimum": -10000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true, + "title": "Channel width (MHz)", + "description": "Populated by selected channel (if set)" + }, + "tx_power": { + "type": "integer", + "maximum": 127, + "minimum": -40, + "nullable": true, + "title": "Transmit power (dBm)" + }, + "untagged_vlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tagged_vlans": { + "type": "array", + "items": { + "type": "integer" + } + }, + "qinq_svlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vlan_translation_policy": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "wireless_lans": { + "type": "array", + "items": { + "type": "integer" + } + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name", + "type" + ] + }, + "BulkInterfaceTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "virtual", + "bridge", + "lag", + "100base-fx", + "100base-lfx", + "100base-tx", + "100base-t1", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cwdm", + "1000base-cx", + "1000base-dwdm", + "1000base-ex", + "1000base-lsx", + "1000base-lx", + "1000base-lx10", + "1000base-sx", + "1000base-t", + "1000base-tx", + "1000base-zx", + "2.5gbase-t", + "5gbase-t", + "10gbase-br-d", + "10gbase-br-u", + "10gbase-cu", + "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "40gbase-sr4-bd", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-cwdm4", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", + "1.6tbase-cr8", + "1.6tbase-dr8", + "1.6tbase-dr8-2", + "100base-x-sfp", + "1000base-x-gbic", + "1000base-x-sfp", + "2.5gbase-x-sfp", + "10gbase-x-sfpp", + "10gbase-x-xenpak", + "10gbase-x-xfp", + "10gbase-x-x2", + "25gbase-x-sfp28", + "40gbase-x-qsfpp", + "50gbase-x-sfp28", + "50gbase-x-sfp56", + "100gbase-x-cfp", + "100gbase-x-cfp2", + "100gbase-x-cfp4", + "100gbase-x-cxp", + "100gbase-x-cpak", + "100gbase-x-dsfp", + "100gbase-x-qsfp28", + "100gbase-x-qsfpdd", + "100gbase-x-sfpdd", + "200gbase-x-cfp2", + "200gbase-x-qsfp56", + "200gbase-x-qsfpdd", + "400gbase-x-qsfp112", + "400gbase-x-qsfpdd", + "400gbase-x-cdfp", + "400gbase-x-cfp2", + "400gbase-x-cfp8", + "400gbase-x-osfp", + "400gbase-x-osfp-rhs", + "800gbase-x-osfp", + "800gbase-x-qsfpdd", + "1.6tbase-x-osfp1600", + "1.6tbase-x-osfp1600-rhs", + "1.6tbase-x-qsfpdd1600", + "1000base-kx", + "2.5gbase-kx", + "5gbase-kr", + "10gbase-kr", + "10gbase-kx4", + "25gbase-kr", + "40gbase-kr4", + "50gbase-kr", + "100gbase-kp4", + "100gbase-kr2", + "100gbase-kr4", + "1.6tbase-kr8", + "ieee802.11a", + "ieee802.11g", + "ieee802.11n", + "ieee802.11ac", + "ieee802.11ad", + "ieee802.11ax", + "ieee802.11ay", + "ieee802.11be", + "ieee802.15.1", + "ieee802.15.4", + "other-wireless", + "gsm", + "cdma", + "lte", + "4g", + "5g", + "sonet-oc3", + "sonet-oc12", + "sonet-oc48", + "sonet-oc192", + "sonet-oc768", + "sonet-oc1920", + "sonet-oc3840", + "1gfc-sfp", + "2gfc-sfp", + "4gfc-sfp", + "8gfc-sfpp", + "16gfc-sfpp", + "32gfc-sfp28", + "32gfc-sfpp", + "64gfc-qsfpp", + "64gfc-sfpdd", + "64gfc-sfpp", + "128gfc-qsfp28", + "infiniband-sdr", + "infiniband-ddr", + "infiniband-qdr", + "infiniband-fdr10", + "infiniband-fdr", + "infiniband-edr", + "infiniband-hdr", + "infiniband-ndr", + "infiniband-xdr", + "t1", + "e1", + "t3", + "e3", + "xdsl", + "docsis", + "moca", + "bpon", + "epon", + "10g-epon", + "gpon", + "xg-pon", + "xgs-pon", + "ng-pon2", + "25g-pon", + "50g-pon", + "cisco-stackwise", + "cisco-stackwise-plus", + "cisco-flexstack", + "cisco-flexstack-plus", + "cisco-stackwise-80", + "cisco-stackwise-160", + "cisco-stackwise-320", + "cisco-stackwise-480", + "cisco-stackwise-1t", + "juniper-vcp", + "extreme-summitstack", + "extreme-summitstack-128", + "extreme-summitstack-256", + "extreme-summitstack-512", + "other" + ], + "type": "string", + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cu` - 10GBASE-CU (10GE DAC Passive Twinax)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `40gbase-sr4-bd` - 40GBASE-SR4 (40GE BiDi)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `1.6tbase-cr8` - 1.6TBASE-CR8 (1.6TE)\n* `1.6tbase-dr8` - 1.6TBASE-DR8 (1.6TE)\n* `1.6tbase-dr8-2` - 1.6TBASE-DR8-2 (1.6TE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `2.5gbase-x-sfp` - SFP (2.5GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1.6tbase-x-osfp1600` - OSFP1600 (1.6TE)\n* `1.6tbase-x-osfp1600-rhs` - OSFP1600-RHS (1.6TE)\n* `1.6tbase-x-qsfpdd1600` - QSFP-DD1600 (1.6TE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `1.6tbase-kr8` - 1.6TBASE-KR8 (1.6TE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "b067eb1f050c6ae9" + }, + "enabled": { + "type": "boolean" + }, + "mgmt_only": { + "type": "boolean", + "title": "Management only" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "bridge": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedInterfaceTemplateRequest" + } + ], + "nullable": true + }, + "poe_mode": { + "enum": [ + "pd", + "pse", + "", + null + ], + "type": "string", + "description": "* `pd` - PD\n* `pse` - PSE", + "x-spec-enum-id": "2f2fe6dcdc7772bd", + "nullable": true + }, + "poe_type": { + "enum": [ + "type1-ieee802.3af", + "type2-ieee802.3at", + "type3-ieee802.3bt", + "type4-ieee802.3bt", + "passive-24v-2pair", + "passive-24v-4pair", + "passive-48v-2pair", + "passive-48v-4pair", + "", + null + ], + "type": "string", + "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)", + "x-spec-enum-id": "5473d57885f237ab", + "nullable": true + }, + "rf_role": { + "enum": [ + "ap", + "station", + "", + null + ], + "type": "string", + "description": "* `ap` - Access point\n* `station` - Station", + "x-spec-enum-id": "d2772dbea88b0fb1", + "nullable": true + } + }, + "required": [ + "id", + "name", + "type" + ] + }, + "BulkInventoryItemRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "parent": { + "type": "integer", + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "staged", + "failed", + "decommissioning" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "545817eb4c4f2ae4" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefInventoryItemRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "part_id": { + "type": "string", + "description": "Manufacturer-assigned part identifier", + "maxLength": 50 + }, + "serial": { + "type": "string", + "title": "Serial number", + "maxLength": 50 + }, + "asset_tag": { + "type": "string", + "nullable": true, + "description": "A unique tag used to identify this item", + "maxLength": 50 + }, + "discovered": { + "type": "boolean", + "description": "This item was automatically discovered" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "component_type": { + "type": "string", + "nullable": true + }, + "component_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name" + ] + }, + "BulkInventoryItemRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "minLength": 1, + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkInventoryItemTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ] + }, + "parent": { + "type": "integer", + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefInventoryItemRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "part_id": { + "type": "string", + "description": "Manufacturer-assigned part identifier", + "maxLength": 50 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "component_type": { + "type": "string", + "nullable": true + }, + "component_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + } + }, + "required": [ + "device_type", + "id", + "name" + ] + }, + "BulkJournalEntryRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "assigned_object_type": { + "type": "string" + }, + "assigned_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "created_by": { + "type": "integer", + "nullable": true + }, + "kind": { + "enum": [ + "info", + "success", + "warning", + "danger" + ], + "type": "string", + "description": "* `info` - Info\n* `success` - Success\n* `warning` - Warning\n* `danger` - Danger", + "x-spec-enum-id": "6f65abe0aab2c78c" + }, + "comments": { + "type": "string", + "minLength": 1 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "assigned_object_id", + "assigned_object_type", + "comments", + "id" + ] + }, + "BulkL2VPNRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "identifier": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "format": "int64", + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "type": { + "enum": [ + "vpws", + "vpls", + "vxlan", + "vxlan-evpn", + "mpls-evpn", + "pbb-evpn", + "evpn-vpws", + "epl", + "evpl", + "ep-lan", + "evp-lan", + "ep-tree", + "evp-tree", + "spb" + ], + "type": "string", + "description": "* `vpws` - VPWS\n* `vpls` - VPLS\n* `vxlan` - VXLAN\n* `vxlan-evpn` - VXLAN-EVPN\n* `mpls-evpn` - MPLS EVPN\n* `pbb-evpn` - PBB EVPN\n* `evpn-vpws` - EVPN VPWS\n* `epl` - EPL\n* `evpl` - EVPL\n* `ep-lan` - Ethernet Private LAN\n* `evp-lan` - Ethernet Virtual Private LAN\n* `ep-tree` - Ethernet Private Tree\n* `evp-tree` - Ethernet Virtual Private Tree\n* `spb` - SPB", + "x-spec-enum-id": "0a46f8056d717efc" + }, + "status": { + "enum": [ + "active", + "planned", + "decommissioning" + ], + "type": "string", + "description": "* `active` - Active\n* `planned` - Planned\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "8b9dc8efc7c3d5b0" + }, + "import_targets": { + "type": "array", + "items": { + "type": "integer" + } + }, + "export_targets": { + "type": "array", + "items": { + "type": "integer" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkL2VPNTerminationRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "l2vpn": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefL2VPNRequest" + } + ] + }, + "assigned_object_type": { + "type": "string" + }, + "assigned_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "assigned_object_id", + "assigned_object_type", + "id", + "l2vpn" + ] + }, + "BulkLocationRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ] + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedLocationRequest" + } + ], + "nullable": true + }, + "status": { + "enum": [ + "planned", + "staging", + "active", + "decommissioning", + "retired" + ], + "type": "string", + "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired", + "x-spec-enum-id": "1cf60831fbb35e7f" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "facility": { + "type": "string", + "description": "Local facility ID or description", + "maxLength": 50 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "site", + "slug" + ] + }, + "BulkMACAddressRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "mac_address": { + "type": "string", + "minLength": 1 + }, + "assigned_object_type": { + "type": "string", + "nullable": true + }, + "assigned_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "mac_address" + ] + }, + "BulkManufacturerRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkModuleBayRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "position": { + "type": "string", + "description": "Identifier to reference when renaming installed components", + "maxLength": 30 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "installed_module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name" + ] + }, + "BulkModuleBayTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "position": { + "type": "string", + "description": "Identifier to reference when renaming installed components", + "maxLength": 30 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkModuleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module_bay": { + "$ref": "#/components/schemas/NestedModuleBayRequest" + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ] + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "staged", + "failed", + "decommissioning" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "545817eb4c4f2ae4" + }, + "serial": { + "type": "string", + "title": "Serial number", + "maxLength": 50 + }, + "asset_tag": { + "type": "string", + "nullable": true, + "description": "A unique tag used to identify this device", + "maxLength": 50 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "replicate_components": { + "type": "boolean", + "writeOnly": true, + "default": true, + "description": "Automatically populate components associated with this module type (default: true)" + }, + "adopt_components": { + "type": "boolean", + "writeOnly": true, + "default": false, + "description": "Adopt already existing components" + } + }, + "required": [ + "device", + "id", + "module_bay", + "module_type" + ] + }, + "BulkModuleTypeProfileRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "schema": { + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkModuleTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "profile": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeProfileRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ] + }, + "model": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "part_number": { + "type": "string", + "description": "Discrete part number (optional)", + "maxLength": 50 + }, + "airflow": { + "enum": [ + "front-to-rear", + "rear-to-front", + "left-to-right", + "right-to-left", + "side-to-rear", + "passive", + "", + null + ], + "type": "string", + "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `passive` - Passive", + "x-spec-enum-id": "5ad4e700c656b09d", + "nullable": true + }, + "weight": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "weight_unit": { + "enum": [ + "kg", + "g", + "lb", + "oz", + "", + null + ], + "type": "string", + "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces", + "x-spec-enum-id": "2235ce3f404afbc0", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "attributes": { + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "manufacturer", + "model" + ] + }, + "BulkNotificationGroupRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "users": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkNotificationRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + }, + "read": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "event_type": { + "enum": [ + "object_created", + "object_updated", + "object_deleted", + "job_started", + "job_completed", + "job_failed", + "job_errored" + ], + "type": "string", + "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored", + "x-spec-enum-id": "01e557313a5c7bd2", + "title": "Event" + } + }, + "required": [ + "event_type", + "id", + "object_id", + "object_type", + "user" + ] + }, + "BulkObjectPermissionRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "enabled": { + "type": "boolean" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "actions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 30 + }, + "description": "The list of actions granted by this permission" + }, + "constraints": { + "nullable": true, + "description": "Queryset filter matching the applicable objects of the selected type(s)" + }, + "groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "users": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "actions", + "id", + "name", + "object_types" + ] + }, + "BulkOwnerGroupRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkOwnerRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "user_groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "users": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "group", + "id", + "name" + ] + }, + "BulkPlatformRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedPlatformRequest" + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "config_template": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkPowerFeedRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "power_panel": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefPowerPanelRequest" + } + ] + }, + "rack": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "failed" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `failed` - Failed", + "x-spec-enum-id": "ec530572dc778583" + }, + "type": { + "enum": [ + "primary", + "redundant" + ], + "type": "string", + "description": "* `primary` - Primary\n* `redundant` - Redundant", + "x-spec-enum-id": "093a164236819eb8" + }, + "supply": { + "enum": [ + "ac", + "dc" + ], + "type": "string", + "description": "* `ac` - AC\n* `dc` - DC", + "x-spec-enum-id": "1b6d99616ca6412b" + }, + "phase": { + "enum": [ + "single-phase", + "three-phase" + ], + "type": "string", + "description": "* `single-phase` - Single phase\n* `three-phase` - Three-phase", + "x-spec-enum-id": "994bc0696f4df57f" + }, + "voltage": { + "type": "integer", + "maximum": 32767, + "minimum": -32768 + }, + "amperage": { + "type": "integer", + "maximum": 32767, + "minimum": 1 + }, + "max_utilization": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Maximum permissible draw (percentage)" + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "power_panel" + ] + }, + "BulkPowerOutletRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "iec-60320-c5", + "iec-60320-c7", + "iec-60320-c13", + "iec-60320-c15", + "iec-60320-c17", + "iec-60320-c19", + "iec-60320-c21", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15r", + "nema-5-15r", + "nema-5-20r", + "nema-5-30r", + "nema-5-50r", + "nema-6-15r", + "nema-6-20r", + "nema-6-30r", + "nema-6-50r", + "nema-10-30r", + "nema-10-50r", + "nema-14-20r", + "nema-14-30r", + "nema-14-50r", + "nema-14-60r", + "nema-15-15r", + "nema-15-20r", + "nema-15-30r", + "nema-15-50r", + "nema-15-60r", + "nema-l1-15r", + "nema-l5-15r", + "nema-l5-20r", + "nema-l5-30r", + "nema-l5-50r", + "nema-l6-15r", + "nema-l6-20r", + "nema-l6-30r", + "nema-l6-50r", + "nema-l10-30r", + "nema-l14-20r", + "nema-l14-30r", + "nema-l14-50r", + "nema-l14-60r", + "nema-l15-20r", + "nema-l15-30r", + "nema-l15-50r", + "nema-l15-60r", + "nema-l21-20r", + "nema-l21-30r", + "nema-l22-20r", + "nema-l22-30r", + "CS6360C", + "CS6364C", + "CS8164C", + "CS8264C", + "CS8364C", + "CS8464C", + "ita-e", + "ita-f", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "ita-multistandard", + "usb-a", + "usb-micro-b", + "usb-c", + "molex-micro-fit-1x2", + "molex-micro-fit-2x2", + "molex-micro-fit-2x3", + "molex-micro-fit-2x4", + "dc-terminal", + "eaton-c39", + "hdot-cx", + "saf-d-grid", + "neutrik-powercon-20a", + "neutrik-powercon-32a", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other", + "", + null + ], + "type": "string", + "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other", + "x-spec-enum-id": "db3e4eb2b93615f8", + "nullable": true + }, + "status": { + "enum": [ + "enabled", + "disabled", + "faulty" + ], + "type": "string", + "description": "* `enabled` - Enabled\n* `disabled` - Disabled\n* `faulty` - Faulty", + "x-spec-enum-id": "d60dce16858f3c69" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "power_port": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPowerPortRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "feed_leg": { + "enum": [ + "A", + "B", + "C", + "", + null + ], + "type": "string", + "description": "* `A` - A\n* `B` - B\n* `C` - C", + "x-spec-enum-id": "a4902339df0b7c06", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name" + ] + }, + "BulkPowerOutletTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "iec-60320-c5", + "iec-60320-c7", + "iec-60320-c13", + "iec-60320-c15", + "iec-60320-c17", + "iec-60320-c19", + "iec-60320-c21", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15r", + "nema-5-15r", + "nema-5-20r", + "nema-5-30r", + "nema-5-50r", + "nema-6-15r", + "nema-6-20r", + "nema-6-30r", + "nema-6-50r", + "nema-10-30r", + "nema-10-50r", + "nema-14-20r", + "nema-14-30r", + "nema-14-50r", + "nema-14-60r", + "nema-15-15r", + "nema-15-20r", + "nema-15-30r", + "nema-15-50r", + "nema-15-60r", + "nema-l1-15r", + "nema-l5-15r", + "nema-l5-20r", + "nema-l5-30r", + "nema-l5-50r", + "nema-l6-15r", + "nema-l6-20r", + "nema-l6-30r", + "nema-l6-50r", + "nema-l10-30r", + "nema-l14-20r", + "nema-l14-30r", + "nema-l14-50r", + "nema-l14-60r", + "nema-l15-20r", + "nema-l15-30r", + "nema-l15-50r", + "nema-l15-60r", + "nema-l21-20r", + "nema-l21-30r", + "nema-l22-20r", + "nema-l22-30r", + "CS6360C", + "CS6364C", + "CS8164C", + "CS8264C", + "CS8364C", + "CS8464C", + "ita-e", + "ita-f", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "ita-multistandard", + "usb-a", + "usb-micro-b", + "usb-c", + "molex-micro-fit-1x2", + "molex-micro-fit-2x2", + "molex-micro-fit-2x3", + "molex-micro-fit-2x4", + "dc-terminal", + "eaton-c39", + "hdot-cx", + "saf-d-grid", + "neutrik-powercon-20a", + "neutrik-powercon-32a", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other", + "", + null + ], + "type": "string", + "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other", + "x-spec-enum-id": "db3e4eb2b93615f8", + "nullable": true + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "power_port": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPowerPortTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "feed_leg": { + "enum": [ + "A", + "B", + "C", + "", + null + ], + "type": "string", + "description": "* `A` - A\n* `B` - B\n* `C` - C", + "x-spec-enum-id": "a4902339df0b7c06", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkPowerPanelRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ] + }, + "location": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefLocationRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "site" + ] + }, + "BulkPowerPortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "iec-60320-c6", + "iec-60320-c8", + "iec-60320-c14", + "iec-60320-c16", + "iec-60320-c18", + "iec-60320-c20", + "iec-60320-c22", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15p", + "nema-5-15p", + "nema-5-20p", + "nema-5-30p", + "nema-5-50p", + "nema-6-15p", + "nema-6-20p", + "nema-6-30p", + "nema-6-50p", + "nema-10-30p", + "nema-10-50p", + "nema-14-20p", + "nema-14-30p", + "nema-14-50p", + "nema-14-60p", + "nema-15-15p", + "nema-15-20p", + "nema-15-30p", + "nema-15-50p", + "nema-15-60p", + "nema-l1-15p", + "nema-l5-15p", + "nema-l5-20p", + "nema-l5-30p", + "nema-l5-50p", + "nema-l6-15p", + "nema-l6-20p", + "nema-l6-30p", + "nema-l6-50p", + "nema-l10-30p", + "nema-l14-20p", + "nema-l14-30p", + "nema-l14-50p", + "nema-l14-60p", + "nema-l15-20p", + "nema-l15-30p", + "nema-l15-50p", + "nema-l15-60p", + "nema-l21-20p", + "nema-l21-30p", + "nema-l22-20p", + "nema-l22-30p", + "cs6361c", + "cs6365c", + "cs8165c", + "cs8265c", + "cs8365c", + "cs8465c", + "ita-c", + "ita-e", + "ita-f", + "ita-ef", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "usb-3-b", + "usb-3-micro-b", + "molex-micro-fit-1x2", + "molex-micro-fit-2x2", + "molex-micro-fit-2x3", + "molex-micro-fit-2x4", + "dc-terminal", + "saf-d-grid", + "neutrik-powercon-20", + "neutrik-powercon-32", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other", + "", + null + ], + "type": "string", + "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other", + "x-spec-enum-id": "aadcbe6ca854c1ed", + "nullable": true + }, + "maximum_draw": { + "type": "integer", + "maximum": 2147483647, + "minimum": 1, + "nullable": true, + "description": "Maximum power draw (watts)" + }, + "allocated_draw": { + "type": "integer", + "maximum": 2147483647, + "minimum": 1, + "nullable": true, + "description": "Allocated power draw (watts)" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name" + ] + }, + "BulkPowerPortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "iec-60320-c6", + "iec-60320-c8", + "iec-60320-c14", + "iec-60320-c16", + "iec-60320-c18", + "iec-60320-c20", + "iec-60320-c22", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15p", + "nema-5-15p", + "nema-5-20p", + "nema-5-30p", + "nema-5-50p", + "nema-6-15p", + "nema-6-20p", + "nema-6-30p", + "nema-6-50p", + "nema-10-30p", + "nema-10-50p", + "nema-14-20p", + "nema-14-30p", + "nema-14-50p", + "nema-14-60p", + "nema-15-15p", + "nema-15-20p", + "nema-15-30p", + "nema-15-50p", + "nema-15-60p", + "nema-l1-15p", + "nema-l5-15p", + "nema-l5-20p", + "nema-l5-30p", + "nema-l5-50p", + "nema-l6-15p", + "nema-l6-20p", + "nema-l6-30p", + "nema-l6-50p", + "nema-l10-30p", + "nema-l14-20p", + "nema-l14-30p", + "nema-l14-50p", + "nema-l14-60p", + "nema-l15-20p", + "nema-l15-30p", + "nema-l15-50p", + "nema-l15-60p", + "nema-l21-20p", + "nema-l21-30p", + "nema-l22-20p", + "nema-l22-30p", + "cs6361c", + "cs6365c", + "cs8165c", + "cs8265c", + "cs8365c", + "cs8465c", + "ita-c", + "ita-e", + "ita-f", + "ita-ef", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "usb-3-b", + "usb-3-micro-b", + "molex-micro-fit-1x2", + "molex-micro-fit-2x2", + "molex-micro-fit-2x3", + "molex-micro-fit-2x4", + "dc-terminal", + "saf-d-grid", + "neutrik-powercon-20", + "neutrik-powercon-32", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other", + "", + null + ], + "type": "string", + "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other", + "x-spec-enum-id": "aadcbe6ca854c1ed", + "nullable": true + }, + "maximum_draw": { + "type": "integer", + "maximum": 2147483647, + "minimum": 1, + "nullable": true, + "description": "Maximum power draw (watts)" + }, + "allocated_draw": { + "type": "integer", + "maximum": 2147483647, + "minimum": 1, + "nullable": true, + "description": "Allocated power draw (watts)" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkPrefixRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "prefix": { + "type": "string", + "minLength": 1 + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "scope_type": { + "type": "string", + "nullable": true + }, + "scope_id": { + "type": "integer", + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "container", + "active", + "reserved", + "deprecated" + ], + "type": "string", + "description": "* `container` - Container\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated", + "x-spec-enum-id": "026173ce39f2ee63" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "is_pool": { + "type": "boolean", + "title": "Is a pool", + "description": "All IP addresses within this prefix are considered usable" + }, + "mark_utilized": { + "type": "boolean", + "description": "Treat as fully utilized" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "prefix" + ] + }, + "BulkProviderAccountRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "provider": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefProviderRequest" + } + ] + }, + "name": { + "type": "string", + "default": "", + "maxLength": 100 + }, + "account": { + "type": "string", + "minLength": 1, + "title": "Account ID", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "account", + "id", + "provider" + ] + }, + "BulkProviderNetworkRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "provider": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefProviderRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "service_id": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "provider" + ] + }, + "BulkProviderRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Full name of the provider", + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "accounts": { + "type": "array", + "items": { + "type": "integer" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "asns": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkRIRRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "is_private": { + "type": "boolean", + "title": "Private", + "description": "IP space managed by this RIR is considered private" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkRackGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkRackRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "facility_id": { + "type": "string", + "nullable": true, + "maxLength": 50 + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ] + }, + "location": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefLocationRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "reserved", + "available", + "planned", + "active", + "deprecated" + ], + "type": "string", + "description": "* `reserved` - Reserved\n* `available` - Available\n* `planned` - Planned\n* `active` - Active\n* `deprecated` - Deprecated", + "x-spec-enum-id": "76eea4eef8804bcb" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "serial": { + "type": "string", + "title": "Serial number", + "maxLength": 50 + }, + "asset_tag": { + "type": "string", + "nullable": true, + "description": "A unique tag used to identify this rack", + "maxLength": 50 + }, + "rack_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "form_factor": { + "enum": [ + "2-post-frame", + "4-post-frame", + "4-post-cabinet", + "wall-frame", + "wall-frame-vertical", + "wall-cabinet", + "wall-cabinet-vertical", + "", + null + ], + "type": "string", + "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)", + "x-spec-enum-id": "8a902fde21d48841", + "nullable": true + }, + "width": { + "enum": [ + 10, + 19, + 21, + 23 + ], + "type": "integer", + "description": "* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches", + "x-spec-enum-id": "9b322795f297a9c3" + }, + "u_height": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "title": "Height (U)", + "description": "Height in rack units" + }, + "starting_unit": { + "type": "integer", + "maximum": 32767, + "minimum": 1, + "description": "Starting unit for rack" + }, + "weight": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "max_weight": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "description": "Maximum load capacity for the rack" + }, + "weight_unit": { + "enum": [ + "kg", + "g", + "lb", + "oz", + "", + null + ], + "type": "string", + "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces", + "x-spec-enum-id": "2235ce3f404afbc0", + "nullable": true + }, + "desc_units": { + "type": "boolean", + "title": "Descending units", + "description": "Units are numbered top-to-bottom" + }, + "outer_width": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (width)" + }, + "outer_height": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (height)" + }, + "outer_depth": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (depth)" + }, + "outer_unit": { + "enum": [ + "mm", + "in", + "", + null + ], + "type": "string", + "description": "* `mm` - Millimeters\n* `in` - Inches", + "x-spec-enum-id": "3d701848b66312c3", + "nullable": true + }, + "mounting_depth": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails." + }, + "airflow": { + "enum": [ + "front-to-rear", + "rear-to-front", + "" + ], + "type": "string", + "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front", + "x-spec-enum-id": "a784734d07ef1b3c" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "site" + ] + }, + "BulkRackReservationRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "rack": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefRackRequest" + } + ] + }, + "units": { + "type": "array", + "items": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + } + }, + "status": { + "enum": [ + "pending", + "active", + "stale" + ], + "type": "string", + "description": "* `pending` - Pending\n* `active` - Active\n* `stale` - Stale", + "x-spec-enum-id": "ed6038a4deee151c" + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "description", + "id", + "rack", + "units", + "user" + ] + }, + "BulkRackRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "minLength": 1, + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkRackTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ] + }, + "model": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "form_factor": { + "enum": [ + "2-post-frame", + "4-post-frame", + "4-post-cabinet", + "wall-frame", + "wall-frame-vertical", + "wall-cabinet", + "wall-cabinet-vertical", + "", + null + ], + "type": "string", + "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)", + "x-spec-enum-id": "8a902fde21d48841", + "nullable": true + }, + "width": { + "enum": [ + 10, + 19, + 21, + 23 + ], + "type": "integer", + "description": "* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches", + "x-spec-enum-id": "9b322795f297a9c3" + }, + "u_height": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "title": "Height (U)", + "description": "Height in rack units" + }, + "starting_unit": { + "type": "integer", + "maximum": 32767, + "minimum": 1, + "description": "Starting unit for rack" + }, + "desc_units": { + "type": "boolean", + "title": "Descending units", + "description": "Units are numbered top-to-bottom" + }, + "outer_width": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (width)" + }, + "outer_height": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (height)" + }, + "outer_depth": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (depth)" + }, + "outer_unit": { + "enum": [ + "mm", + "in", + "", + null + ], + "type": "string", + "description": "* `mm` - Millimeters\n* `in` - Inches", + "x-spec-enum-id": "3d701848b66312c3", + "nullable": true + }, + "weight": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "max_weight": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "description": "Maximum load capacity for the rack" + }, + "weight_unit": { + "enum": [ + "kg", + "g", + "lb", + "oz", + "", + null + ], + "type": "string", + "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces", + "x-spec-enum-id": "2235ce3f404afbc0", + "nullable": true + }, + "mounting_depth": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails." + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "manufacturer", + "model", + "slug" + ] + }, + "BulkRearPortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "fc-pc", + "fc-upc", + "fc-apc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ], + "type": "string", + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "positions": { + "type": "integer", + "maximum": 1024, + "minimum": 1 + }, + "front_ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RearPortMappingRequest" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name", + "type" + ] + }, + "BulkRearPortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "fc-pc", + "fc-upc", + "fc-apc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ], + "type": "string", + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "positions": { + "type": "integer", + "maximum": 1024, + "minimum": 1 + }, + "front_ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RearPortTemplateMappingRequest" + } + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "name", + "type" + ] + }, + "BulkRegionRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedRegionRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkRouteTargetRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Route target value (formatted in accordance with RFC 4360)", + "maxLength": 21 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkSavedFilterRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "user": { + "type": "integer", + "nullable": true + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "enabled": { + "type": "boolean" + }, + "shared": { + "type": "boolean" + }, + "parameters": {}, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + } + }, + "required": [ + "id", + "name", + "object_types", + "parameters", + "slug" + ] + }, + "BulkServiceRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "parent_object_type": { + "type": "string" + }, + "parent_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "protocol": { + "enum": [ + "tcp", + "udp", + "sctp" + ], + "type": "string", + "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP", + "x-spec-enum-id": "e4b15bec749a2a32" + }, + "ports": { + "type": "array", + "items": { + "type": "integer", + "maximum": 65535, + "minimum": 1 + }, + "title": "Port numbers" + }, + "ipaddresses": { + "type": "array", + "items": { + "type": "integer" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "parent_object_id", + "parent_object_type", + "ports" + ] + }, + "BulkServiceTemplateRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "protocol": { + "enum": [ + "tcp", + "udp", + "sctp" + ], + "type": "string", + "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP", + "x-spec-enum-id": "e4b15bec749a2a32" + }, + "ports": { + "type": "array", + "items": { + "type": "integer", + "maximum": 65535, + "minimum": 1 + }, + "title": "Port numbers" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "ports" + ] + }, + "BulkSiteGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedSiteGroupRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkSiteRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Full name of the site", + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "status": { + "enum": [ + "planned", + "staging", + "active", + "decommissioning", + "retired" + ], + "type": "string", + "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired", + "x-spec-enum-id": "1cf60831fbb35e7f" + }, + "region": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRegionRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefSiteGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "facility": { + "type": "string", + "description": "Local facility ID or description", + "maxLength": 50 + }, + "time_zone": { + "type": "string", + "nullable": true, + "minLength": 1 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "physical_address": { + "type": "string", + "description": "Physical location of the building", + "maxLength": 200 + }, + "shipping_address": { + "type": "string", + "description": "If different from the physical address", + "maxLength": 200 + }, + "latitude": { + "type": "number", + "format": "double", + "maximum": 90.0, + "minimum": -90.0, + "nullable": true, + "description": "GPS coordinate in decimal format (xx.yyyyyy)" + }, + "longitude": { + "type": "number", + "format": "double", + "maximum": 180.0, + "minimum": -180.0, + "nullable": true, + "description": "GPS coordinate in decimal format (xx.yyyyyy)" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "asns": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkSubscriptionRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + } + }, + "required": [ + "id", + "object_id", + "object_type", + "user" + ] + }, + "BulkTableConfigRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "table": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "user": { + "type": "integer", + "nullable": true + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "enabled": { + "type": "boolean" + }, + "shared": { + "type": "boolean" + }, + "columns": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 100 + } + }, + "ordering": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "nullable": true + } + }, + "required": [ + "columns", + "id", + "name", + "object_type", + "table" + ] + }, + "BulkTagRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "pattern": "^[-\\w]+$", + "maxLength": 100 + }, + "color": { + "type": "string", + "minLength": 1, + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkTenantGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedTenantGroupRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkTenantRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkTokenRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "version": { + "enum": [ + 1, + 2 + ], + "type": "integer", + "description": "* `1` - v1\n* `2` - v2", + "x-spec-enum-id": "b5df70f0bffd12cb", + "minimum": 0, + "maximum": 32767 + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "expires": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_used": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "enabled": { + "type": "boolean", + "description": "Disable to temporarily revoke this token without deleting it." + }, + "write_enabled": { + "type": "boolean", + "description": "Permit create/update/delete operations using this token" + }, + "pepper_id": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "ID of the cryptographic pepper used to hash the token (v2 only)" + }, + "token": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "user" + ] + }, + "BulkTunnelGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkTunnelRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "status": { + "enum": [ + "planned", + "active", + "disabled" + ], + "type": "string", + "description": "* `planned` - Planned\n* `active` - Active\n* `disabled` - Disabled", + "x-spec-enum-id": "2431ef62c418f485" + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTunnelGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "encapsulation": { + "enum": [ + "ipsec-transport", + "ipsec-tunnel", + "ip-ip", + "gre", + "wireguard", + "openvpn", + "l2tp", + "pptp" + ], + "type": "string", + "description": "* `ipsec-transport` - IPsec - Transport\n* `ipsec-tunnel` - IPsec - Tunnel\n* `ip-ip` - IP-in-IP\n* `gre` - GRE\n* `wireguard` - WireGuard\n* `openvpn` - OpenVPN\n* `l2tp` - L2TP\n* `pptp` - PPTP", + "x-spec-enum-id": "4f3254459f0e94f0" + }, + "ipsec_profile": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPSecProfileRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tunnel_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "encapsulation", + "id", + "name", + "status" + ] + }, + "BulkTunnelTerminationRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "tunnel": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefTunnelRequest" + } + ] + }, + "role": { + "enum": [ + "peer", + "hub", + "spoke" + ], + "type": "string", + "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke", + "x-spec-enum-id": "0b3bfadcebd86b58" + }, + "termination_type": { + "type": "string" + }, + "termination_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "outside_ip": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "role", + "termination_type", + "tunnel" + ] + }, + "BulkUserRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "username": { + "type": "string", + "minLength": 1, + "description": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + "pattern": "^[\\w.@+-]+$", + "maxLength": 150 + }, + "password": { + "type": "string", + "writeOnly": true, + "minLength": 1, + "maxLength": 128 + }, + "first_name": { + "type": "string", + "maxLength": 150 + }, + "last_name": { + "type": "string", + "maxLength": 150 + }, + "email": { + "type": "string", + "format": "email", + "title": "Email address", + "maxLength": 254 + }, + "is_active": { + "type": "boolean", + "title": "Active", + "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts." + }, + "date_joined": { + "type": "string", + "format": "date-time" + }, + "last_login": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "permissions": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id", + "password", + "username" + ] + }, + "BulkVLANGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "scope_type": { + "type": "string", + "nullable": true + }, + "scope_id": { + "type": "integer", + "nullable": true + }, + "vid_ranges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IntegerRangeRequest" + } + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkVLANRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vid": { + "type": "integer", + "maximum": 4094, + "minimum": 1, + "title": "VLAN ID", + "description": "Numeric VLAN ID (1-4094)" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "reserved", + "deprecated" + ], + "type": "string", + "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated", + "x-spec-enum-id": "ca933c38b935e547" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "qinq_role": { + "enum": [ + "svlan", + "cvlan", + null + ], + "type": "string", + "description": "* `svlan` - Service\n* `cvlan` - Customer", + "x-spec-enum-id": "fa0abd59fb1a7312", + "nullable": true + }, + "qinq_svlan": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedVLANRequest" + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "vid" + ] + }, + "BulkVLANTranslationPolicyRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkVLANTranslationRuleRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "policy": { + "type": "integer" + }, + "local_vid": { + "type": "integer", + "maximum": 4094, + "minimum": 1, + "title": "Local VLAN ID", + "description": "Numeric VLAN ID (1-4094)" + }, + "remote_vid": { + "type": "integer", + "maximum": 4094, + "minimum": 1, + "title": "Remote VLAN ID", + "description": "Numeric VLAN ID (1-4094)" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id", + "local_vid", + "policy", + "remote_vid" + ] + }, + "BulkVMInterfaceRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "virtual_machine": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefVirtualMachineRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "enabled": { + "type": "boolean" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedVMInterfaceRequest" + } + ], + "nullable": true + }, + "bridge": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedVMInterfaceRequest" + } + ], + "nullable": true + }, + "mtu": { + "type": "integer", + "maximum": 65536, + "minimum": 1, + "nullable": true + }, + "primary_mac_address": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefMACAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mode": { + "enum": [ + "access", + "tagged", + "tagged-all", + "q-in-q", + "" + ], + "type": "string", + "description": "* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)", + "x-spec-enum-id": "84129b71b974ebe5" + }, + "untagged_vlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tagged_vlans": { + "type": "array", + "items": { + "type": "integer" + } + }, + "qinq_svlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vlan_translation_policy": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "virtual_machine" + ] + }, + "BulkVRFRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "rd": { + "type": "string", + "nullable": true, + "title": "Route distinguisher", + "description": "Unique route distinguisher (as defined in RFC 4364)", + "maxLength": 21 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "enforce_unique": { + "type": "boolean", + "title": "Enforce unique space", + "description": "Prevent duplicate prefixes/IP addresses within this VRF" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "import_targets": { + "type": "array", + "items": { + "type": "integer" + } + }, + "export_targets": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkVirtualChassisRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "domain": { + "type": "string", + "maxLength": 30 + }, + "master": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedDeviceRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkVirtualCircuitRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "cid": { + "type": "string", + "minLength": 1, + "title": "Circuit ID", + "description": "Unique circuit ID", + "maxLength": 100 + }, + "provider_network": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefProviderNetworkRequest" + } + ] + }, + "provider_account": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefProviderAccountRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefVirtualCircuitTypeRequest" + } + ] + }, + "status": { + "enum": [ + "planned", + "provisioning", + "active", + "offline", + "deprovisioning", + "decommissioned" + ], + "type": "string", + "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned", + "x-spec-enum-id": "0a239d878b6666a4" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "cid", + "id", + "provider_network", + "type" + ] + }, + "BulkVirtualCircuitTerminationRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "virtual_circuit": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefVirtualCircuitRequest" + } + ] + }, + "role": { + "enum": [ + "peer", + "hub", + "spoke" + ], + "type": "string", + "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke", + "x-spec-enum-id": "0b3bfadcebd86b58" + }, + "interface": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefInterfaceRequest" + } + ] + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "interface", + "virtual_circuit" + ] + }, + "BulkVirtualCircuitTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkVirtualDeviceContextRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "identifier": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip4": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip6": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "planned", + "offline" + ], + "type": "string", + "description": "* `active` - Active\n* `planned` - Planned\n* `offline` - Offline", + "x-spec-enum-id": "0e2c0919d51b83cb" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "device", + "id", + "name", + "status" + ] + }, + "BulkVirtualDiskRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "virtual_machine": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefVirtualMachineRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "size": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "size", + "virtual_machine" + ] + }, + "BulkVirtualMachineTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "default_platform": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPlatformRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "default_vcpus": { + "type": "number", + "format": "double", + "maximum": 10000, + "minimum": 0.01, + "exclusiveMaximum": true, + "nullable": true + }, + "default_memory": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "Default memory (MB)" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkVirtualMachineWithConfigContextRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "virtual_machine_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVirtualMachineTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "staged", + "failed", + "decommissioning", + "paused" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning\n* `paused` - Paused", + "x-spec-enum-id": "effecc3b94e0b74b" + }, + "start_on_boot": { + "enum": [ + "on", + "off", + "laststate" + ], + "type": "string", + "description": "* `on` - On\n* `off` - Off\n* `laststate` - Last State", + "x-spec-enum-id": "610e33fc2fde73d6" + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "cluster": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefClusterRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "platform": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPlatformRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip4": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip6": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vcpus": { + "type": "number", + "format": "double", + "maximum": 10000, + "minimum": 0.01, + "exclusiveMaximum": true, + "nullable": true + }, + "memory": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "disk": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "serial": { + "type": "string", + "title": "Serial number", + "maxLength": 50 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "local_context_data": { + "nullable": true, + "description": "Local config context data takes precedence over source contexts in the final rendered config context" + }, + "config_template": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "name" + ] + }, + "BulkWebhookRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "payload_url": { + "type": "string", + "minLength": 1, + "title": "URL", + "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.", + "maxLength": 500 + }, + "http_method": { + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "type": "string", + "description": "* `GET` - GET\n* `POST` - POST\n* `PUT` - PUT\n* `PATCH` - PATCH\n* `DELETE` - DELETE", + "x-spec-enum-id": "867bf764d3b1eeaa" + }, + "http_content_type": { + "type": "string", + "minLength": 1, + "description": "The complete list of official content types is available here.", + "maxLength": 100 + }, + "additional_headers": { + "type": "string", + "description": "User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. Headers should be defined in the format Name: Value. Jinja2 template processing is supported with the same context as the request body (below)." + }, + "body_template": { + "type": "string", + "description": "Jinja2 template for a custom request body. If blank, a JSON object representing the change will be included. Available context data includes: event, model, timestamp, username, request_id, and data." + }, + "secret": { + "type": "string", + "description": "When provided, the request will include a X-Hook-Signature header containing a HMAC hex digest of the payload body using the secret as the key. The secret is not transmitted in the request.", + "maxLength": 255 + }, + "ssl_verification": { + "type": "boolean", + "description": "Enable SSL certificate verification. Disable with caution!" + }, + "ca_file_path": { + "type": "string", + "nullable": true, + "description": "The specific CA certificate file to use for SSL verification. Leave blank to use the system defaults.", + "maxLength": 4096 + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + } + }, + "required": [ + "id", + "name", + "payload_url" + ] + }, + "BulkWirelessLANGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedWirelessLANGroupRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug" + ] + }, + "BulkWirelessLANRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "ssid": { + "type": "string", + "minLength": 1, + "maxLength": 32 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefWirelessLANGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "reserved", + "disabled", + "deprecated", + "" + ], + "type": "string", + "description": "* `active` - Active\n* `reserved` - Reserved\n* `disabled` - Disabled\n* `deprecated` - Deprecated", + "x-spec-enum-id": "e5549d7370ce2e6c" + }, + "vlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "scope_type": { + "type": "string", + "nullable": true + }, + "scope_id": { + "type": "integer", + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "auth_type": { + "enum": [ + "open", + "wep", + "wpa-personal", + "wpa-enterprise", + "" + ], + "type": "string", + "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise", + "x-spec-enum-id": "e917c12aac765910" + }, + "auth_cipher": { + "enum": [ + "auto", + "tkip", + "aes", + "" + ], + "type": "string", + "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES", + "x-spec-enum-id": "42f867e89988bb0c" + }, + "auth_psk": { + "type": "string", + "title": "Pre-shared key", + "maxLength": 64 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "ssid" + ] + }, + "BulkWirelessLinkRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "interface_a": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefInterfaceRequest" + } + ] + }, + "interface_b": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefInterfaceRequest" + } + ] + }, + "ssid": { + "type": "string", + "maxLength": 32 + }, + "status": { + "enum": [ + "connected", + "planned", + "decommissioning" + ], + "type": "string", + "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "80d251a40f3a3144" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "auth_type": { + "enum": [ + "open", + "wep", + "wpa-personal", + "wpa-enterprise", + "" + ], + "type": "string", + "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise", + "x-spec-enum-id": "e917c12aac765910" + }, + "auth_cipher": { + "enum": [ + "auto", + "tkip", + "aes", + "" + ], + "type": "string", + "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES", + "x-spec-enum-id": "42f867e89988bb0c" + }, + "auth_psk": { + "type": "string", + "title": "Pre-shared key", + "maxLength": 64 + }, + "distance": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "distance_unit": { + "enum": [ + "km", + "m", + "mi", + "ft", + "", + null + ], + "type": "string", + "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet", + "x-spec-enum-id": "b1169a409430c02e", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id", + "interface_a", + "interface_b" + ] + }, "Cable": { "type": "object", "description": "Base serializer class for models inheriting from PrimaryModel.", @@ -244195,6 +264467,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -244229,8 +264505,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "label": { "type": "string", @@ -244261,6 +264537,10 @@ "LC/PC", "LC/UPC", "LC/APC", + "MU", + "MU/PC", + "MU/UPC", + "MU/APC", "LSH", "LSH/PC", "LSH/UPC", @@ -244516,6 +264796,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -244550,8 +264834,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -244684,6 +264968,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -244718,8 +265006,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "label": { "type": "string", @@ -244750,6 +265038,10 @@ "LC/PC", "LC/UPC", "LC/APC", + "MU", + "MU/PC", + "MU/UPC", + "MU/APC", "LSH", "LSH/PC", "LSH/UPC", @@ -244946,6 +265238,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -244980,8 +265276,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -259305,6 +279601,12959 @@ } } }, + "PatchedBulkASNRangeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "rir": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefRIRRequest" + } + ] + }, + "start": { + "type": "integer", + "maximum": 4294967295, + "minimum": 1, + "format": "int64" + }, + "end": { + "type": "integer", + "maximum": 4294967295, + "minimum": 1, + "format": "int64" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkASNRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "asn": { + "type": "integer", + "maximum": 4294967295, + "minimum": 1, + "format": "int64", + "description": "16- or 32-bit autonomous system number" + }, + "rir": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRIRRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "sites": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkAggregateRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "prefix": { + "type": "string", + "minLength": 1 + }, + "rir": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefRIRRequest" + } + ] + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "date_added": { + "type": "string", + "format": "date", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkBookmarkRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCableBundleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCableRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "cat3", + "cat5", + "cat5e", + "cat6", + "cat6a", + "cat7", + "cat7a", + "cat8", + "mrj21-trunk", + "dac-active", + "dac-passive", + "coaxial", + "rg-6", + "rg-8", + "rg-11", + "rg-59", + "rg-62", + "rg-213", + "lmr-100", + "lmr-200", + "lmr-400", + "mmf", + "mmf-om1", + "mmf-om2", + "mmf-om3", + "mmf-om4", + "mmf-om5", + "smf", + "smf-os1", + "smf-os2", + "aoc", + "power", + "usb", + "", + null + ], + "type": "string", + "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `mrj21-trunk` - MRJ21 Trunk\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `coaxial` - Coaxial\n* `rg-6` - RG-6\n* `rg-8` - RG-8\n* `rg-11` - RG-11\n* `rg-59` - RG-59\n* `rg-62` - RG-62\n* `rg-213` - RG-213\n* `lmr-100` - LMR-100\n* `lmr-200` - LMR-200\n* `lmr-400` - LMR-400\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Single-mode Fiber\n* `smf-os1` - Single-mode Fiber (OS1)\n* `smf-os2` - Single-mode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `power` - Power\n* `usb` - USB", + "x-spec-enum-id": "3d4d8d7ae24f7be8", + "nullable": true + }, + "a_terminations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GenericObjectRequest" + } + }, + "b_terminations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GenericObjectRequest" + } + }, + "status": { + "enum": [ + "connected", + "planned", + "decommissioning" + ], + "type": "string", + "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "80d251a40f3a3144" + }, + "profile": { + "enum": [ + "single-1c1p", + "single-1c2p", + "single-1c4p", + "single-1c6p", + "single-1c8p", + "single-1c12p", + "single-1c16p", + "trunk-2c1p", + "trunk-2c2p", + "trunk-2c4p", + "trunk-2c4p-shuffle", + "trunk-2c6p", + "trunk-2c8p", + "trunk-2c12p", + "trunk-4c1p", + "trunk-4c2p", + "trunk-4c4p", + "trunk-4c4p-shuffle", + "trunk-4c6p", + "trunk-4c8p", + "trunk-8c4p", + "breakout-1c2p-2c1p", + "breakout-1c4p-4c1p", + "breakout-1c6p-6c1p", + "breakout-2c4p-8c1p-shuffle" + ], + "type": "string", + "description": "* `single-1c1p` - 1C1P\n* `single-1c2p` - 1C2P\n* `single-1c4p` - 1C4P\n* `single-1c6p` - 1C6P\n* `single-1c8p` - 1C8P\n* `single-1c12p` - 1C12P\n* `single-1c16p` - 1C16P\n* `trunk-2c1p` - 2C1P trunk\n* `trunk-2c2p` - 2C2P trunk\n* `trunk-2c4p` - 2C4P trunk\n* `trunk-2c4p-shuffle` - 2C4P trunk (shuffle)\n* `trunk-2c6p` - 2C6P trunk\n* `trunk-2c8p` - 2C8P trunk\n* `trunk-2c12p` - 2C12P trunk\n* `trunk-4c1p` - 4C1P trunk\n* `trunk-4c2p` - 4C2P trunk\n* `trunk-4c4p` - 4C4P trunk\n* `trunk-4c4p-shuffle` - 4C4P trunk (shuffle)\n* `trunk-4c6p` - 4C6P trunk\n* `trunk-4c8p` - 4C8P trunk\n* `trunk-8c4p` - 8C4P trunk\n* `breakout-1c2p-2c1p` - 1C2P:2C1P breakout\n* `breakout-1c4p-4c1p` - 1C4P:4C1P breakout\n* `breakout-1c6p-6c1p` - 1C6P:6C1P breakout\n* `breakout-2c4p-8c1p-shuffle` - 2C4P:8C1P breakout (shuffle)", + "x-spec-enum-id": "f566e6df6572f5d0" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "bundle": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefCableBundleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "label": { + "type": "string", + "maxLength": 100 + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "length": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "length_unit": { + "enum": [ + "km", + "m", + "cm", + "mi", + "ft", + "in", + "", + null + ], + "type": "string", + "description": "* `km` - Kilometers\n* `m` - Meters\n* `cm` - Centimeters\n* `mi` - Miles\n* `ft` - Feet\n* `in` - Inches", + "x-spec-enum-id": "6e7645525ba02462", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCircuitGroupAssignmentRequest": { + "type": "object", + "description": "Base serializer for group assignments under CircuitSerializer.", + "properties": { + "id": { + "type": "integer" + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefCircuitGroupRequest" + } + ] + }, + "member_type": { + "type": "string" + }, + "member_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "priority": { + "enum": [ + "primary", + "secondary", + "tertiary", + "inactive", + "" + ], + "type": "string", + "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive", + "x-spec-enum-id": "0548fc537440bf9d" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCircuitGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCircuitRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "cid": { + "type": "string", + "minLength": 1, + "title": "Circuit ID", + "description": "Unique circuit ID", + "maxLength": 100 + }, + "provider": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefProviderRequest" + } + ] + }, + "provider_account": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefProviderAccountRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefCircuitTypeRequest" + } + ] + }, + "status": { + "enum": [ + "planned", + "provisioning", + "active", + "offline", + "deprovisioning", + "decommissioned" + ], + "type": "string", + "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned", + "x-spec-enum-id": "0a239d878b6666a4" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "install_date": { + "type": "string", + "format": "date", + "nullable": true, + "title": "Installed" + }, + "termination_date": { + "type": "string", + "format": "date", + "nullable": true, + "title": "Terminates" + }, + "commit_rate": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "Commit rate (Kbps)", + "description": "Committed rate" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "distance": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "distance_unit": { + "enum": [ + "km", + "m", + "mi", + "ft", + "", + null + ], + "type": "string", + "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet", + "x-spec-enum-id": "b1169a409430c02e", + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "assignments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BriefCircuitGroupAssignmentSerializer_Request" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCircuitTerminationRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "circuit": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefCircuitRequest" + } + ] + }, + "term_side": { + "enum": [ + "A", + "Z" + ], + "type": "string", + "description": "* `A` - A\n* `Z` - Z", + "x-spec-enum-id": "95b8fcc737f355d0", + "title": "Termination side" + }, + "termination_type": { + "type": "string", + "nullable": true + }, + "termination_id": { + "type": "integer", + "nullable": true + }, + "port_speed": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "Port speed (Kbps)", + "description": "Physical circuit speed" + }, + "upstream_speed": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "Upstream speed (Kbps)", + "description": "Upstream speed, if different from port speed" + }, + "xconnect_id": { + "type": "string", + "title": "Cross-connect ID", + "description": "ID of the local cross-connect", + "maxLength": 50 + }, + "pp_info": { + "type": "string", + "title": "Patch panel/port(s)", + "description": "Patch panel ID and port number(s)", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCircuitTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkClusterGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkClusterRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefClusterTypeRequest" + } + ] + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefClusterGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "planned", + "staging", + "active", + "decommissioning", + "offline" + ], + "type": "string", + "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `offline` - Offline", + "x-spec-enum-id": "65a25166053759eb" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "scope_type": { + "type": "string", + "nullable": true + }, + "scope_id": { + "type": "integer", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkClusterTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkConfigContextProfileRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "schema": { + "nullable": true, + "description": "A JSON schema specifying the structure of the context data for this profile" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "data_source": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDataSourceRequest" + } + ] + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkConfigContextRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "profile": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigContextProfileRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "is_active": { + "type": "boolean" + }, + "regions": { + "type": "array", + "items": { + "type": "integer" + } + }, + "site_groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "sites": { + "type": "array", + "items": { + "type": "integer" + } + }, + "locations": { + "type": "array", + "items": { + "type": "integer" + } + }, + "device_types": { + "type": "array", + "items": { + "type": "integer" + } + }, + "roles": { + "type": "array", + "items": { + "type": "integer" + } + }, + "platforms": { + "type": "array", + "items": { + "type": "integer" + } + }, + "cluster_types": { + "type": "array", + "items": { + "type": "integer" + } + }, + "cluster_groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "clusters": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tenant_groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tenants": { + "type": "array", + "items": { + "type": "integer" + } + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "data_source": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDataSourceRequest" + } + ] + }, + "data": {} + }, + "required": [ + "id" + ] + }, + "PatchedBulkConfigTemplateRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "environment_params": { + "nullable": true, + "title": "Environment parameters", + "description": "Any additional parameters to pass when constructing the Jinja environment" + }, + "template_code": { + "type": "string", + "minLength": 1, + "description": "Jinja template code." + }, + "mime_type": { + "type": "string", + "description": "Defaults to text/plain; charset=utf-8", + "maxLength": 50 + }, + "file_name": { + "type": "string", + "description": "Filename to give to the rendered export file", + "maxLength": 200 + }, + "file_extension": { + "type": "string", + "description": "Extension to append to the rendered filename", + "maxLength": 15 + }, + "as_attachment": { + "type": "boolean", + "description": "Download file as attachment" + }, + "debug": { + "type": "boolean", + "description": "Enable verbose error output when rendering this template. Not recommended for production use." + }, + "data_source": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDataSourceRequest" + } + ] + }, + "auto_sync_enabled": { + "type": "boolean", + "description": "Enable automatic synchronization of data when the data file is updated" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkConsolePortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other", + "" + ], + "type": "string", + "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "7b8d0e83a4bb5178" + }, + "speed": { + "enum": [ + 1200, + 2400, + 4800, + 9600, + 19200, + 38400, + 57600, + 115200, + null + ], + "type": "integer", + "description": "* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps", + "x-spec-enum-id": "ab6d9635c131a378", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkConsolePortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other", + "" + ], + "type": "string", + "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "7b8d0e83a4bb5178" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkConsoleServerPortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other", + "" + ], + "type": "string", + "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "7b8d0e83a4bb5178" + }, + "speed": { + "enum": [ + 1200, + 2400, + 4800, + 9600, + 19200, + 38400, + 57600, + 115200, + null + ], + "type": "integer", + "description": "* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps", + "x-spec-enum-id": "ab6d9635c131a378", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkConsoleServerPortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other", + "" + ], + "type": "string", + "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "7b8d0e83a4bb5178" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkContactAssignmentRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "contact": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefContactRequest" + } + ] + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefContactRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "priority": { + "enum": [ + "primary", + "secondary", + "tertiary", + "inactive", + "" + ], + "type": "string", + "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive", + "x-spec-enum-id": "0548fc537440bf9d" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkContactGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedContactGroupRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkContactRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "title": { + "type": "string", + "maxLength": 100 + }, + "phone": { + "type": "string", + "maxLength": 50 + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "address": { + "type": "string", + "maxLength": 200 + }, + "link": { + "type": "string", + "format": "uri", + "maxLength": 200 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkContactRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCustomFieldChoiceSetRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "base_choices": { + "enum": [ + "IATA", + "ISO_3166", + "UN_LOCODE" + ], + "type": "string", + "description": "* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)", + "x-spec-enum-id": "cf0efb5195f85007" + }, + "extra_choices": { + "type": "array", + "items": { + "type": "array", + "items": {}, + "maxItems": 2, + "minItems": 2 + } + }, + "choice_colors": { + "type": "object", + "additionalProperties": { + "enum": [ + "blue", + "indigo", + "purple", + "pink", + "red", + "orange", + "yellow", + "green", + "teal", + "cyan", + "gray", + "black", + "white" + ], + "type": "string", + "description": "* `blue` - Blue\n* `indigo` - Indigo\n* `purple` - Purple\n* `pink` - Pink\n* `red` - Red\n* `orange` - Orange\n* `yellow` - Yellow\n* `green` - Green\n* `teal` - Teal\n* `cyan` - Cyan\n* `gray` - Gray\n* `black` - Black\n* `white` - White", + "x-spec-enum-id": "de0a6a124020dfe0" + } + }, + "order_alphabetically": { + "type": "boolean", + "description": "Choices are automatically ordered alphabetically" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCustomFieldRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "enum": [ + "text", + "longtext", + "integer", + "decimal", + "boolean", + "date", + "datetime", + "url", + "json", + "select", + "multiselect", + "object", + "multiobject" + ], + "type": "string", + "description": "* `text` - Text\n* `longtext` - Text (long)\n* `integer` - Integer\n* `decimal` - Decimal\n* `boolean` - Boolean (true/false)\n* `date` - Date\n* `datetime` - Date & time\n* `url` - URL\n* `json` - JSON\n* `select` - Selection\n* `multiselect` - Multiple selection\n* `object` - Object\n* `multiobject` - Multiple objects", + "x-spec-enum-id": "47c52a3d983e924c" + }, + "related_object_type": { + "type": "string", + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Internal field name", + "pattern": "^[a-z0-9_]+$", + "maxLength": 50 + }, + "label": { + "type": "string", + "description": "Name of the field as displayed to users (if not provided, 'the field's name will be used)", + "maxLength": 50 + }, + "group_name": { + "type": "string", + "description": "Custom fields within the same group will be displayed together", + "maxLength": 50 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "required": { + "type": "boolean", + "description": "This field is required when creating new objects or editing an existing object." + }, + "unique": { + "type": "boolean", + "title": "Must be unique", + "description": "The value of this field must be unique for the assigned object" + }, + "search_weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "description": "Weighting for search. Lower values are considered more important. Fields with a search weight of zero will be ignored." + }, + "filter_logic": { + "enum": [ + "disabled", + "loose", + "exact" + ], + "type": "string", + "description": "* `disabled` - Disabled\n* `loose` - Loose\n* `exact` - Exact", + "x-spec-enum-id": "d168820c798ae45a" + }, + "ui_visible": { + "enum": [ + "always", + "if-set", + "hidden" + ], + "type": "string", + "description": "* `always` - Always\n* `if-set` - If set\n* `hidden` - Hidden", + "x-spec-enum-id": "f32800c399b927b6" + }, + "ui_editable": { + "enum": [ + "yes", + "no", + "hidden" + ], + "type": "string", + "description": "* `yes` - Yes\n* `no` - No\n* `hidden` - Hidden", + "x-spec-enum-id": "336f52760e62022f" + }, + "is_cloneable": { + "type": "boolean", + "description": "Replicate this value when cloning objects" + }, + "default": { + "nullable": true, + "description": "Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. \"Foo\")." + }, + "related_object_filter": { + "nullable": true, + "description": "Filter the object selection choices using a query_params dict (must be a JSON value).Encapsulate strings with double quotes (e.g. \"Foo\")." + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "title": "Display weight", + "description": "Fields with higher weights appear lower in a form." + }, + "validation_minimum": { + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true, + "title": "Minimum value", + "description": "Minimum allowed value (for numeric fields)" + }, + "validation_maximum": { + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true, + "title": "Maximum value", + "description": "Maximum allowed value (for numeric fields)" + }, + "validation_regex": { + "type": "string", + "description": "Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. For example, ^[A-Z]{3}$ will limit values to exactly three uppercase letters.", + "maxLength": 500 + }, + "validation_schema": { + "nullable": true, + "description": "A JSON schema definition for validating the custom field value" + }, + "choice_set": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefCustomFieldChoiceSetRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkCustomLinkRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "enabled": { + "type": "boolean" + }, + "link_text": { + "type": "string", + "minLength": 1, + "description": "Jinja2 template code for link text" + }, + "link_url": { + "type": "string", + "minLength": 1, + "description": "Jinja2 template code for link URL" + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "group_name": { + "type": "string", + "description": "Links with the same group will appear as a dropdown menu", + "maxLength": 50 + }, + "button_class": { + "enum": [ + "default", + "blue", + "indigo", + "purple", + "pink", + "red", + "orange", + "yellow", + "green", + "teal", + "cyan", + "gray", + "black", + "white", + "ghost-dark" + ], + "type": "string", + "x-spec-enum-id": "5e54b3bd086685ce", + "description": "The class of the first link in a group will be used for the dropdown button\n\n* `default` - Default\n* `blue` - Blue\n* `indigo` - Indigo\n* `purple` - Purple\n* `pink` - Pink\n* `red` - Red\n* `orange` - Orange\n* `yellow` - Yellow\n* `green` - Green\n* `teal` - Teal\n* `cyan` - Cyan\n* `gray` - Gray\n* `black` - Black\n* `white` - White\n* `ghost-dark` - Link" + }, + "new_window": { + "type": "boolean", + "description": "Force link to open in a new window" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkDataSourceRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "type": { + "enum": [ + null, + "local", + "git", + "amazon-s3" + ], + "description": "* `None` - ---------\n* `local` - Local\n* `git` - Git\n* `amazon-s3` - Amazon S3", + "x-spec-enum-id": "562b613a749b34b0" + }, + "source_url": { + "type": "string", + "minLength": 1, + "title": "URL", + "maxLength": 200 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "sync_interval": { + "enum": [ + 1, + 60, + 720, + 1440, + 10080, + 43200, + null + ], + "type": "integer", + "description": "* `1` - Minutely\n* `60` - Hourly\n* `720` - 12 hours\n* `1440` - Daily\n* `10080` - Weekly\n* `43200` - 30 days", + "x-spec-enum-id": "2e9f2567ecd93fbe", + "nullable": true, + "minimum": 0, + "maximum": 32767 + }, + "parameters": { + "nullable": true + }, + "ignore_rules": { + "type": "string", + "description": "Patterns (one per line) matching files or paths to ignore when syncing" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkDeviceBayRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "installed_device": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkDeviceBayTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkDeviceRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "minLength": 1, + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "vm_role": { + "type": "boolean", + "description": "Virtual machines may be assigned to this role" + }, + "config_template": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedDeviceRoleRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkDeviceTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ] + }, + "default_platform": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPlatformRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "model": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "part_number": { + "type": "string", + "description": "Discrete part number (optional)", + "maxLength": 50 + }, + "u_height": { + "type": "number", + "format": "double", + "maximum": 1000, + "minimum": 0.0, + "exclusiveMaximum": true, + "default": 1.0, + "title": "Position (U)" + }, + "exclude_from_utilization": { + "type": "boolean", + "description": "Devices of this type are excluded when calculating rack utilization." + }, + "is_full_depth": { + "type": "boolean", + "description": "Device consumes both front and rear rack faces." + }, + "subdevice_role": { + "enum": [ + "parent", + "child", + "", + null + ], + "type": "string", + "description": "* `parent` - Parent\n* `child` - Child", + "x-spec-enum-id": "65a61d5e1deb4a24", + "nullable": true + }, + "airflow": { + "enum": [ + "front-to-rear", + "rear-to-front", + "left-to-right", + "right-to-left", + "side-to-rear", + "rear-to-side", + "bottom-to-top", + "top-to-bottom", + "passive", + "mixed", + "", + null + ], + "type": "string", + "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed", + "x-spec-enum-id": "11cb3d363b41ba9e", + "nullable": true + }, + "weight": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "weight_unit": { + "enum": [ + "kg", + "g", + "lb", + "oz", + "", + null + ], + "type": "string", + "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces", + "x-spec-enum-id": "2235ce3f404afbc0", + "nullable": true + }, + "front_image": { + "type": "string", + "format": "binary", + "nullable": true + }, + "rear_image": { + "type": "string", + "format": "binary", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkDeviceWithConfigContextRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "nullable": true, + "maxLength": 64 + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ] + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRoleRequest" + } + ] + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "platform": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPlatformRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "serial": { + "type": "string", + "title": "Serial number", + "description": "Chassis serial number, assigned by the manufacturer", + "maxLength": 50 + }, + "asset_tag": { + "type": "string", + "nullable": true, + "description": "A unique tag used to identify this device", + "maxLength": 50 + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ] + }, + "location": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefLocationRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "rack": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "position": { + "type": "number", + "format": "double", + "maximum": 1000, + "minimum": 0.5, + "exclusiveMaximum": true, + "nullable": true, + "title": "Position (U)" + }, + "face": { + "enum": [ + "front", + "rear", + "" + ], + "type": "string", + "description": "* `front` - Front\n* `rear` - Rear", + "x-spec-enum-id": "d2fb9b3f75158b83" + }, + "latitude": { + "type": "number", + "format": "double", + "maximum": 90.0, + "minimum": -90.0, + "nullable": true, + "description": "GPS coordinate in decimal format (xx.yyyyyy)" + }, + "longitude": { + "type": "number", + "format": "double", + "maximum": 180.0, + "minimum": -180.0, + "nullable": true, + "description": "GPS coordinate in decimal format (xx.yyyyyy)" + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "staged", + "failed", + "inventory", + "decommissioning" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `inventory` - Inventory\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "65feb4244cc9110c" + }, + "airflow": { + "enum": [ + "front-to-rear", + "rear-to-front", + "left-to-right", + "right-to-left", + "side-to-rear", + "rear-to-side", + "bottom-to-top", + "top-to-bottom", + "passive", + "mixed", + "" + ], + "type": "string", + "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed", + "x-spec-enum-id": "11cb3d363b41ba9e" + }, + "primary_ip4": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip6": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "oob_ip": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "cluster": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefClusterRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "virtual_chassis": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVirtualChassisRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vc_position": { + "type": "integer", + "maximum": 255, + "minimum": 0, + "nullable": true + }, + "vc_priority": { + "type": "integer", + "maximum": 255, + "minimum": 0, + "nullable": true, + "description": "Virtual chassis master election priority" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "config_template": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "local_context_data": { + "nullable": true, + "description": "Local config context data takes precedence over source contexts in the final rendered config context" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkEventRuleRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "enabled": { + "type": "boolean" + }, + "event_types": { + "type": "array", + "items": { + "enum": [ + "object_created", + "object_updated", + "object_deleted", + "job_started", + "job_completed", + "job_failed", + "job_errored" + ], + "type": "string", + "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored", + "x-spec-enum-id": "01e557313a5c7bd2" + }, + "description": "The types of event which will trigger this rule." + }, + "conditions": { + "nullable": true, + "description": "A set of conditions which determine whether the event will be generated." + }, + "action_type": { + "enum": [ + "webhook", + "script", + "notification" + ], + "type": "string", + "description": "* `webhook` - Webhook\n* `script` - Script\n* `notification` - Notification", + "x-spec-enum-id": "287901b937995956" + }, + "action_object_type": { + "type": "string" + }, + "action_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkExportTemplateRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "environment_params": { + "nullable": true, + "title": "Environment parameters", + "description": "Any additional parameters to pass when constructing the Jinja environment" + }, + "template_code": { + "type": "string", + "minLength": 1, + "description": "Jinja template code." + }, + "mime_type": { + "type": "string", + "description": "Defaults to text/plain; charset=utf-8", + "maxLength": 50 + }, + "file_name": { + "type": "string", + "description": "Filename to give to the rendered export file", + "maxLength": 200 + }, + "file_extension": { + "type": "string", + "description": "Extension to append to the rendered filename", + "maxLength": 15 + }, + "as_attachment": { + "type": "boolean", + "description": "Download file as attachment" + }, + "data_source": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDataSourceRequest" + } + ] + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkFHRPGroupAssignmentRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefFHRPGroupRequest" + } + ] + }, + "interface_type": { + "type": "string" + }, + "interface_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "priority": { + "type": "integer", + "maximum": 255, + "minimum": 0 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkFHRPGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "maxLength": 100 + }, + "protocol": { + "enum": [ + "vrrp2", + "vrrp3", + "carp", + "clusterxl", + "hsrp", + "glbp", + "other" + ], + "type": "string", + "description": "* `vrrp2` - VRRPv2\n* `vrrp3` - VRRPv3\n* `carp` - CARP\n* `clusterxl` - ClusterXL\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `other` - Other", + "x-spec-enum-id": "98de93c9f65d1c65" + }, + "group_id": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "auth_type": { + "enum": [ + "plaintext", + "md5", + "", + null + ], + "type": "string", + "description": "* `plaintext` - Plaintext\n* `md5` - MD5", + "x-spec-enum-id": "565396e386e1542a", + "nullable": true, + "title": "Authentication type" + }, + "auth_key": { + "type": "string", + "title": "Authentication key", + "maxLength": 255 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkFrontPortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "fc-pc", + "fc-upc", + "fc-apc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ], + "type": "string", + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "positions": { + "type": "integer", + "maximum": 1024, + "minimum": 1 + }, + "rear_ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FrontPortMappingRequest" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkFrontPortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "fc-pc", + "fc-upc", + "fc-apc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ], + "type": "string", + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "positions": { + "type": "integer", + "maximum": 1024, + "minimum": 1 + }, + "rear_ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FrontPortTemplateMappingRequest" + } + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkGroupRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "permissions": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkIKEPolicyRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "version": { + "enum": [ + 1, + 2 + ], + "type": "integer", + "description": "* `1` - IKEv1\n* `2` - IKEv2", + "x-spec-enum-id": "00872b77916a1fde" + }, + "mode": { + "enum": [ + "aggressive", + "main" + ], + "type": "string", + "description": "* `aggressive` - Aggressive\n* `main` - Main", + "x-spec-enum-id": "64c1be7bdb2548ca" + }, + "proposals": { + "type": "array", + "items": { + "type": "integer" + } + }, + "preshared_key": { + "type": "string", + "title": "Pre-shared key" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkIKEProposalRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "authentication_method": { + "enum": [ + "preshared-keys", + "certificates", + "rsa-signatures", + "dsa-signatures" + ], + "type": "string", + "description": "* `preshared-keys` - Pre-shared keys\n* `certificates` - Certificates\n* `rsa-signatures` - RSA signatures\n* `dsa-signatures` - DSA signatures", + "x-spec-enum-id": "a21158c52d0c455a" + }, + "encryption_algorithm": { + "enum": [ + "aes-128-cbc", + "aes-128-gcm", + "aes-192-cbc", + "aes-192-gcm", + "aes-256-cbc", + "aes-256-gcm", + "3des-cbc", + "des-cbc" + ], + "type": "string", + "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES", + "x-spec-enum-id": "ae3dabd7b2b3cba2" + }, + "authentication_algorithm": { + "enum": [ + "hmac-sha1", + "hmac-sha256", + "hmac-sha384", + "hmac-sha512", + "hmac-md5" + ], + "type": "string", + "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC", + "x-spec-enum-id": "0a7ca69695b483a7" + }, + "group": { + "enum": [ + 1, + 2, + 5, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34 + ], + "type": "integer", + "description": "* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34", + "x-spec-enum-id": "dbef43be795462a8" + }, + "sa_lifetime": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "description": "Security association lifetime (in seconds)" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkIPAddressRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "address": { + "type": "string", + "minLength": 1 + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "reserved", + "deprecated", + "dhcp", + "slaac" + ], + "type": "string", + "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated\n* `dhcp` - DHCP\n* `slaac` - SLAAC", + "x-spec-enum-id": "c421c4c4a0fa7a2a" + }, + "role": { + "enum": [ + "loopback", + "secondary", + "anycast", + "vip", + "vrrp", + "hsrp", + "glbp", + "carp", + "" + ], + "type": "string", + "description": "* `loopback` - Loopback\n* `secondary` - Secondary\n* `anycast` - Anycast\n* `vip` - VIP\n* `vrrp` - VRRP\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `carp` - CARP", + "x-spec-enum-id": "53dca4cddd7b344a" + }, + "assigned_object_type": { + "type": "string", + "nullable": true + }, + "assigned_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "nat_inside": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedIPAddressRequest" + } + ], + "nullable": true + }, + "dns_name": { + "type": "string", + "description": "Hostname or FQDN (not case-sensitive)", + "pattern": "^([0-9A-Za-z_-]+|\\*)(\\.[0-9A-Za-z_-]+)*\\.?$", + "maxLength": 255 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkIPRangeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "start_address": { + "type": "string", + "minLength": 1 + }, + "end_address": { + "type": "string", + "minLength": 1 + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "reserved", + "deprecated" + ], + "type": "string", + "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated", + "x-spec-enum-id": "ca933c38b935e547" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "mark_populated": { + "type": "boolean", + "description": "Prevent the creation of IP addresses within this range" + }, + "mark_utilized": { + "type": "boolean", + "description": "Report space as fully utilized" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkIPSecPolicyRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "proposals": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pfs_group": { + "enum": [ + 1, + 2, + 5, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34 + ], + "type": "integer", + "description": "* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34", + "x-spec-enum-id": "dbef43be795462a8" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkIPSecProfileRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mode": { + "enum": [ + "esp", + "ah" + ], + "type": "string", + "description": "* `esp` - ESP\n* `ah` - AH", + "x-spec-enum-id": "87ac6ada0da14ccf" + }, + "ike_policy": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefIKEPolicyRequest" + } + ] + }, + "ipsec_policy": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefIPSecPolicyRequest" + } + ] + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkIPSecProposalRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "encryption_algorithm": { + "enum": [ + "aes-128-cbc", + "aes-128-gcm", + "aes-192-cbc", + "aes-192-gcm", + "aes-256-cbc", + "aes-256-gcm", + "3des-cbc", + "des-cbc" + ], + "type": "string", + "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES", + "x-spec-enum-id": "ae3dabd7b2b3cba2" + }, + "authentication_algorithm": { + "enum": [ + "hmac-sha1", + "hmac-sha256", + "hmac-sha384", + "hmac-sha512", + "hmac-md5" + ], + "type": "string", + "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC", + "x-spec-enum-id": "0a7ca69695b483a7" + }, + "sa_lifetime_seconds": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "SA lifetime (seconds)", + "description": "Security association lifetime (seconds)" + }, + "sa_lifetime_data": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "SA lifetime (KB)", + "description": "Security association lifetime (in kilobytes)" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkImageAttachmentRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "name": { + "type": "string", + "maxLength": 50 + }, + "image": { + "type": "string", + "format": "binary" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkInterfaceRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "vdcs": { + "type": "array", + "items": { + "type": "integer" + } + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "virtual", + "bridge", + "lag", + "100base-fx", + "100base-lfx", + "100base-tx", + "100base-t1", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cwdm", + "1000base-cx", + "1000base-dwdm", + "1000base-ex", + "1000base-lsx", + "1000base-lx", + "1000base-lx10", + "1000base-sx", + "1000base-t", + "1000base-tx", + "1000base-zx", + "2.5gbase-t", + "5gbase-t", + "10gbase-br-d", + "10gbase-br-u", + "10gbase-cu", + "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "40gbase-sr4-bd", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-cwdm4", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", + "1.6tbase-cr8", + "1.6tbase-dr8", + "1.6tbase-dr8-2", + "100base-x-sfp", + "1000base-x-gbic", + "1000base-x-sfp", + "2.5gbase-x-sfp", + "10gbase-x-sfpp", + "10gbase-x-xenpak", + "10gbase-x-xfp", + "10gbase-x-x2", + "25gbase-x-sfp28", + "40gbase-x-qsfpp", + "50gbase-x-sfp28", + "50gbase-x-sfp56", + "100gbase-x-cfp", + "100gbase-x-cfp2", + "100gbase-x-cfp4", + "100gbase-x-cxp", + "100gbase-x-cpak", + "100gbase-x-dsfp", + "100gbase-x-qsfp28", + "100gbase-x-qsfpdd", + "100gbase-x-sfpdd", + "200gbase-x-cfp2", + "200gbase-x-qsfp56", + "200gbase-x-qsfpdd", + "400gbase-x-qsfp112", + "400gbase-x-qsfpdd", + "400gbase-x-cdfp", + "400gbase-x-cfp2", + "400gbase-x-cfp8", + "400gbase-x-osfp", + "400gbase-x-osfp-rhs", + "800gbase-x-osfp", + "800gbase-x-qsfpdd", + "1.6tbase-x-osfp1600", + "1.6tbase-x-osfp1600-rhs", + "1.6tbase-x-qsfpdd1600", + "1000base-kx", + "2.5gbase-kx", + "5gbase-kr", + "10gbase-kr", + "10gbase-kx4", + "25gbase-kr", + "40gbase-kr4", + "50gbase-kr", + "100gbase-kp4", + "100gbase-kr2", + "100gbase-kr4", + "1.6tbase-kr8", + "ieee802.11a", + "ieee802.11g", + "ieee802.11n", + "ieee802.11ac", + "ieee802.11ad", + "ieee802.11ax", + "ieee802.11ay", + "ieee802.11be", + "ieee802.15.1", + "ieee802.15.4", + "other-wireless", + "gsm", + "cdma", + "lte", + "4g", + "5g", + "sonet-oc3", + "sonet-oc12", + "sonet-oc48", + "sonet-oc192", + "sonet-oc768", + "sonet-oc1920", + "sonet-oc3840", + "1gfc-sfp", + "2gfc-sfp", + "4gfc-sfp", + "8gfc-sfpp", + "16gfc-sfpp", + "32gfc-sfp28", + "32gfc-sfpp", + "64gfc-qsfpp", + "64gfc-sfpdd", + "64gfc-sfpp", + "128gfc-qsfp28", + "infiniband-sdr", + "infiniband-ddr", + "infiniband-qdr", + "infiniband-fdr10", + "infiniband-fdr", + "infiniband-edr", + "infiniband-hdr", + "infiniband-ndr", + "infiniband-xdr", + "t1", + "e1", + "t3", + "e3", + "xdsl", + "docsis", + "moca", + "bpon", + "epon", + "10g-epon", + "gpon", + "xg-pon", + "xgs-pon", + "ng-pon2", + "25g-pon", + "50g-pon", + "cisco-stackwise", + "cisco-stackwise-plus", + "cisco-flexstack", + "cisco-flexstack-plus", + "cisco-stackwise-80", + "cisco-stackwise-160", + "cisco-stackwise-320", + "cisco-stackwise-480", + "cisco-stackwise-1t", + "juniper-vcp", + "extreme-summitstack", + "extreme-summitstack-128", + "extreme-summitstack-256", + "extreme-summitstack-512", + "other" + ], + "type": "string", + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cu` - 10GBASE-CU (10GE DAC Passive Twinax)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `40gbase-sr4-bd` - 40GBASE-SR4 (40GE BiDi)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `1.6tbase-cr8` - 1.6TBASE-CR8 (1.6TE)\n* `1.6tbase-dr8` - 1.6TBASE-DR8 (1.6TE)\n* `1.6tbase-dr8-2` - 1.6TBASE-DR8-2 (1.6TE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `2.5gbase-x-sfp` - SFP (2.5GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1.6tbase-x-osfp1600` - OSFP1600 (1.6TE)\n* `1.6tbase-x-osfp1600-rhs` - OSFP1600-RHS (1.6TE)\n* `1.6tbase-x-qsfpdd1600` - QSFP-DD1600 (1.6TE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `1.6tbase-kr8` - 1.6TBASE-KR8 (1.6TE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "b067eb1f050c6ae9" + }, + "enabled": { + "type": "boolean" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedInterfaceRequest" + } + ], + "nullable": true + }, + "bridge": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedInterfaceRequest" + } + ], + "nullable": true + }, + "lag": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedInterfaceRequest" + } + ], + "nullable": true + }, + "mtu": { + "type": "integer", + "maximum": 65536, + "minimum": 1, + "nullable": true + }, + "primary_mac_address": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefMACAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "speed": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true, + "title": "Speed (Kbps)" + }, + "duplex": { + "enum": [ + "half", + "full", + "auto", + "", + null + ], + "type": "string", + "description": "* `half` - Half\n* `full` - Full\n* `auto` - Auto", + "x-spec-enum-id": "368458a2b67c916b", + "nullable": true + }, + "wwn": { + "type": "string", + "nullable": true + }, + "mgmt_only": { + "type": "boolean", + "title": "Management only", + "description": "This interface is used only for out-of-band management" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mode": { + "enum": [ + "access", + "tagged", + "tagged-all", + "q-in-q", + "" + ], + "type": "string", + "description": "* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)", + "x-spec-enum-id": "84129b71b974ebe5" + }, + "rf_role": { + "enum": [ + "ap", + "station", + "" + ], + "type": "string", + "description": "* `ap` - Access point\n* `station` - Station", + "x-spec-enum-id": "d2772dbea88b0fb1" + }, + "rf_channel": { + "enum": [ + "2.4g-1-2412-22", + "2.4g-2-2417-22", + "2.4g-3-2422-22", + "2.4g-4-2427-22", + "2.4g-5-2432-22", + "2.4g-6-2437-22", + "2.4g-7-2442-22", + "2.4g-8-2447-22", + "2.4g-9-2452-22", + "2.4g-10-2457-22", + "2.4g-11-2462-22", + "2.4g-12-2467-22", + "2.4g-13-2472-22", + "5g-32-5160-20", + "5g-34-5170-40", + "5g-36-5180-20", + "5g-38-5190-40", + "5g-40-5200-20", + "5g-42-5210-80", + "5g-44-5220-20", + "5g-46-5230-40", + "5g-48-5240-20", + "5g-50-5250-160", + "5g-52-5260-20", + "5g-54-5270-40", + "5g-56-5280-20", + "5g-58-5290-80", + "5g-60-5300-20", + "5g-62-5310-40", + "5g-64-5320-20", + "5g-100-5500-20", + "5g-102-5510-40", + "5g-104-5520-20", + "5g-106-5530-80", + "5g-108-5540-20", + "5g-110-5550-40", + "5g-112-5560-20", + "5g-114-5570-160", + "5g-116-5580-20", + "5g-118-5590-40", + "5g-120-5600-20", + "5g-122-5610-80", + "5g-124-5620-20", + "5g-126-5630-40", + "5g-128-5640-20", + "5g-132-5660-20", + "5g-134-5670-40", + "5g-136-5680-20", + "5g-138-5690-80", + "5g-140-5700-20", + "5g-142-5710-40", + "5g-144-5720-20", + "5g-149-5745-20", + "5g-151-5755-40", + "5g-153-5765-20", + "5g-155-5775-80", + "5g-157-5785-20", + "5g-159-5795-40", + "5g-161-5805-20", + "5g-163-5815-160", + "5g-165-5825-20", + "5g-167-5835-40", + "5g-169-5845-20", + "5g-171-5855-80", + "5g-173-5865-20", + "5g-175-5875-40", + "5g-177-5885-20", + "6g-1-5955-20", + "6g-3-5965-40", + "6g-5-5975-20", + "6g-7-5985-80", + "6g-9-5995-20", + "6g-11-6005-40", + "6g-13-6015-20", + "6g-15-6025-160", + "6g-17-6035-20", + "6g-19-6045-40", + "6g-21-6055-20", + "6g-23-6065-80", + "6g-25-6075-20", + "6g-27-6085-40", + "6g-29-6095-20", + "6g-31-6105-320", + "6g-33-6115-20", + "6g-35-6125-40", + "6g-37-6135-20", + "6g-39-6145-80", + "6g-41-6155-20", + "6g-43-6165-40", + "6g-45-6175-20", + "6g-47-6185-160", + "6g-49-6195-20", + "6g-51-6205-40", + "6g-53-6215-20", + "6g-55-6225-80", + "6g-57-6235-20", + "6g-59-6245-40", + "6g-61-6255-20", + "6g-65-6275-20", + "6g-67-6285-40", + "6g-69-6295-20", + "6g-71-6305-80", + "6g-73-6315-20", + "6g-75-6325-40", + "6g-77-6335-20", + "6g-79-6345-160", + "6g-81-6355-20", + "6g-83-6365-40", + "6g-85-6375-20", + "6g-87-6385-80", + "6g-89-6395-20", + "6g-91-6405-40", + "6g-93-6415-20", + "6g-95-6425-320", + "6g-97-6435-20", + "6g-99-6445-40", + "6g-101-6455-20", + "6g-103-6465-80", + "6g-105-6475-20", + "6g-107-6485-40", + "6g-109-6495-20", + "6g-111-6505-160", + "6g-113-6515-20", + "6g-115-6525-40", + "6g-117-6535-20", + "6g-119-6545-80", + "6g-121-6555-20", + "6g-123-6565-40", + "6g-125-6575-20", + "6g-129-6595-20", + "6g-131-6605-40", + "6g-133-6615-20", + "6g-135-6625-80", + "6g-137-6635-20", + "6g-139-6645-40", + "6g-141-6655-20", + "6g-143-6665-160", + "6g-145-6675-20", + "6g-147-6685-40", + "6g-149-6695-20", + "6g-151-6705-80", + "6g-153-6715-20", + "6g-155-6725-40", + "6g-157-6735-20", + "6g-159-6745-320", + "6g-161-6755-20", + "6g-163-6765-40", + "6g-165-6775-20", + "6g-167-6785-80", + "6g-169-6795-20", + "6g-171-6805-40", + "6g-173-6815-20", + "6g-175-6825-160", + "6g-177-6835-20", + "6g-179-6845-40", + "6g-181-6855-20", + "6g-183-6865-80", + "6g-185-6875-20", + "6g-187-6885-40", + "6g-189-6895-20", + "6g-193-6915-20", + "6g-195-6925-40", + "6g-197-6935-20", + "6g-199-6945-80", + "6g-201-6955-20", + "6g-203-6965-40", + "6g-205-6975-20", + "6g-207-6985-160", + "6g-209-6995-20", + "6g-211-7005-40", + "6g-213-7015-20", + "6g-215-7025-80", + "6g-217-7035-20", + "6g-219-7045-40", + "6g-221-7055-20", + "6g-225-7075-20", + "6g-227-7085-40", + "6g-229-7095-20", + "6g-233-7115-20", + "60g-1-58320-2160", + "60g-2-60480-2160", + "60g-3-62640-2160", + "60g-4-64800-2160", + "60g-5-66960-2160", + "60g-6-69120-2160", + "60g-9-59400-4320", + "60g-10-61560-4320", + "60g-11-63720-4320", + "60g-12-65880-4320", + "60g-13-68040-4320", + "60g-17-60480-6480", + "60g-18-62640-6480", + "60g-19-64800-6480", + "60g-20-66960-6480", + "60g-25-61560-6480", + "60g-26-63720-6480", + "60g-27-65880-6480", + "" + ], + "type": "string", + "description": "* `2.4g-1-2412-22` - 1 (2412 MHz)\n* `2.4g-2-2417-22` - 2 (2417 MHz)\n* `2.4g-3-2422-22` - 3 (2422 MHz)\n* `2.4g-4-2427-22` - 4 (2427 MHz)\n* `2.4g-5-2432-22` - 5 (2432 MHz)\n* `2.4g-6-2437-22` - 6 (2437 MHz)\n* `2.4g-7-2442-22` - 7 (2442 MHz)\n* `2.4g-8-2447-22` - 8 (2447 MHz)\n* `2.4g-9-2452-22` - 9 (2452 MHz)\n* `2.4g-10-2457-22` - 10 (2457 MHz)\n* `2.4g-11-2462-22` - 11 (2462 MHz)\n* `2.4g-12-2467-22` - 12 (2467 MHz)\n* `2.4g-13-2472-22` - 13 (2472 MHz)\n* `5g-32-5160-20` - 32 (5160/20 MHz)\n* `5g-34-5170-40` - 34 (5170/40 MHz)\n* `5g-36-5180-20` - 36 (5180/20 MHz)\n* `5g-38-5190-40` - 38 (5190/40 MHz)\n* `5g-40-5200-20` - 40 (5200/20 MHz)\n* `5g-42-5210-80` - 42 (5210/80 MHz)\n* `5g-44-5220-20` - 44 (5220/20 MHz)\n* `5g-46-5230-40` - 46 (5230/40 MHz)\n* `5g-48-5240-20` - 48 (5240/20 MHz)\n* `5g-50-5250-160` - 50 (5250/160 MHz)\n* `5g-52-5260-20` - 52 (5260/20 MHz)\n* `5g-54-5270-40` - 54 (5270/40 MHz)\n* `5g-56-5280-20` - 56 (5280/20 MHz)\n* `5g-58-5290-80` - 58 (5290/80 MHz)\n* `5g-60-5300-20` - 60 (5300/20 MHz)\n* `5g-62-5310-40` - 62 (5310/40 MHz)\n* `5g-64-5320-20` - 64 (5320/20 MHz)\n* `5g-100-5500-20` - 100 (5500/20 MHz)\n* `5g-102-5510-40` - 102 (5510/40 MHz)\n* `5g-104-5520-20` - 104 (5520/20 MHz)\n* `5g-106-5530-80` - 106 (5530/80 MHz)\n* `5g-108-5540-20` - 108 (5540/20 MHz)\n* `5g-110-5550-40` - 110 (5550/40 MHz)\n* `5g-112-5560-20` - 112 (5560/20 MHz)\n* `5g-114-5570-160` - 114 (5570/160 MHz)\n* `5g-116-5580-20` - 116 (5580/20 MHz)\n* `5g-118-5590-40` - 118 (5590/40 MHz)\n* `5g-120-5600-20` - 120 (5600/20 MHz)\n* `5g-122-5610-80` - 122 (5610/80 MHz)\n* `5g-124-5620-20` - 124 (5620/20 MHz)\n* `5g-126-5630-40` - 126 (5630/40 MHz)\n* `5g-128-5640-20` - 128 (5640/20 MHz)\n* `5g-132-5660-20` - 132 (5660/20 MHz)\n* `5g-134-5670-40` - 134 (5670/40 MHz)\n* `5g-136-5680-20` - 136 (5680/20 MHz)\n* `5g-138-5690-80` - 138 (5690/80 MHz)\n* `5g-140-5700-20` - 140 (5700/20 MHz)\n* `5g-142-5710-40` - 142 (5710/40 MHz)\n* `5g-144-5720-20` - 144 (5720/20 MHz)\n* `5g-149-5745-20` - 149 (5745/20 MHz)\n* `5g-151-5755-40` - 151 (5755/40 MHz)\n* `5g-153-5765-20` - 153 (5765/20 MHz)\n* `5g-155-5775-80` - 155 (5775/80 MHz)\n* `5g-157-5785-20` - 157 (5785/20 MHz)\n* `5g-159-5795-40` - 159 (5795/40 MHz)\n* `5g-161-5805-20` - 161 (5805/20 MHz)\n* `5g-163-5815-160` - 163 (5815/160 MHz)\n* `5g-165-5825-20` - 165 (5825/20 MHz)\n* `5g-167-5835-40` - 167 (5835/40 MHz)\n* `5g-169-5845-20` - 169 (5845/20 MHz)\n* `5g-171-5855-80` - 171 (5855/80 MHz)\n* `5g-173-5865-20` - 173 (5865/20 MHz)\n* `5g-175-5875-40` - 175 (5875/40 MHz)\n* `5g-177-5885-20` - 177 (5885/20 MHz)\n* `6g-1-5955-20` - 1 (5955/20 MHz)\n* `6g-3-5965-40` - 3 (5965/40 MHz)\n* `6g-5-5975-20` - 5 (5975/20 MHz)\n* `6g-7-5985-80` - 7 (5985/80 MHz)\n* `6g-9-5995-20` - 9 (5995/20 MHz)\n* `6g-11-6005-40` - 11 (6005/40 MHz)\n* `6g-13-6015-20` - 13 (6015/20 MHz)\n* `6g-15-6025-160` - 15 (6025/160 MHz)\n* `6g-17-6035-20` - 17 (6035/20 MHz)\n* `6g-19-6045-40` - 19 (6045/40 MHz)\n* `6g-21-6055-20` - 21 (6055/20 MHz)\n* `6g-23-6065-80` - 23 (6065/80 MHz)\n* `6g-25-6075-20` - 25 (6075/20 MHz)\n* `6g-27-6085-40` - 27 (6085/40 MHz)\n* `6g-29-6095-20` - 29 (6095/20 MHz)\n* `6g-31-6105-320` - 31 (6105/320 MHz)\n* `6g-33-6115-20` - 33 (6115/20 MHz)\n* `6g-35-6125-40` - 35 (6125/40 MHz)\n* `6g-37-6135-20` - 37 (6135/20 MHz)\n* `6g-39-6145-80` - 39 (6145/80 MHz)\n* `6g-41-6155-20` - 41 (6155/20 MHz)\n* `6g-43-6165-40` - 43 (6165/40 MHz)\n* `6g-45-6175-20` - 45 (6175/20 MHz)\n* `6g-47-6185-160` - 47 (6185/160 MHz)\n* `6g-49-6195-20` - 49 (6195/20 MHz)\n* `6g-51-6205-40` - 51 (6205/40 MHz)\n* `6g-53-6215-20` - 53 (6215/20 MHz)\n* `6g-55-6225-80` - 55 (6225/80 MHz)\n* `6g-57-6235-20` - 57 (6235/20 MHz)\n* `6g-59-6245-40` - 59 (6245/40 MHz)\n* `6g-61-6255-20` - 61 (6255/20 MHz)\n* `6g-65-6275-20` - 65 (6275/20 MHz)\n* `6g-67-6285-40` - 67 (6285/40 MHz)\n* `6g-69-6295-20` - 69 (6295/20 MHz)\n* `6g-71-6305-80` - 71 (6305/80 MHz)\n* `6g-73-6315-20` - 73 (6315/20 MHz)\n* `6g-75-6325-40` - 75 (6325/40 MHz)\n* `6g-77-6335-20` - 77 (6335/20 MHz)\n* `6g-79-6345-160` - 79 (6345/160 MHz)\n* `6g-81-6355-20` - 81 (6355/20 MHz)\n* `6g-83-6365-40` - 83 (6365/40 MHz)\n* `6g-85-6375-20` - 85 (6375/20 MHz)\n* `6g-87-6385-80` - 87 (6385/80 MHz)\n* `6g-89-6395-20` - 89 (6395/20 MHz)\n* `6g-91-6405-40` - 91 (6405/40 MHz)\n* `6g-93-6415-20` - 93 (6415/20 MHz)\n* `6g-95-6425-320` - 95 (6425/320 MHz)\n* `6g-97-6435-20` - 97 (6435/20 MHz)\n* `6g-99-6445-40` - 99 (6445/40 MHz)\n* `6g-101-6455-20` - 101 (6455/20 MHz)\n* `6g-103-6465-80` - 103 (6465/80 MHz)\n* `6g-105-6475-20` - 105 (6475/20 MHz)\n* `6g-107-6485-40` - 107 (6485/40 MHz)\n* `6g-109-6495-20` - 109 (6495/20 MHz)\n* `6g-111-6505-160` - 111 (6505/160 MHz)\n* `6g-113-6515-20` - 113 (6515/20 MHz)\n* `6g-115-6525-40` - 115 (6525/40 MHz)\n* `6g-117-6535-20` - 117 (6535/20 MHz)\n* `6g-119-6545-80` - 119 (6545/80 MHz)\n* `6g-121-6555-20` - 121 (6555/20 MHz)\n* `6g-123-6565-40` - 123 (6565/40 MHz)\n* `6g-125-6575-20` - 125 (6575/20 MHz)\n* `6g-129-6595-20` - 129 (6595/20 MHz)\n* `6g-131-6605-40` - 131 (6605/40 MHz)\n* `6g-133-6615-20` - 133 (6615/20 MHz)\n* `6g-135-6625-80` - 135 (6625/80 MHz)\n* `6g-137-6635-20` - 137 (6635/20 MHz)\n* `6g-139-6645-40` - 139 (6645/40 MHz)\n* `6g-141-6655-20` - 141 (6655/20 MHz)\n* `6g-143-6665-160` - 143 (6665/160 MHz)\n* `6g-145-6675-20` - 145 (6675/20 MHz)\n* `6g-147-6685-40` - 147 (6685/40 MHz)\n* `6g-149-6695-20` - 149 (6695/20 MHz)\n* `6g-151-6705-80` - 151 (6705/80 MHz)\n* `6g-153-6715-20` - 153 (6715/20 MHz)\n* `6g-155-6725-40` - 155 (6725/40 MHz)\n* `6g-157-6735-20` - 157 (6735/20 MHz)\n* `6g-159-6745-320` - 159 (6745/320 MHz)\n* `6g-161-6755-20` - 161 (6755/20 MHz)\n* `6g-163-6765-40` - 163 (6765/40 MHz)\n* `6g-165-6775-20` - 165 (6775/20 MHz)\n* `6g-167-6785-80` - 167 (6785/80 MHz)\n* `6g-169-6795-20` - 169 (6795/20 MHz)\n* `6g-171-6805-40` - 171 (6805/40 MHz)\n* `6g-173-6815-20` - 173 (6815/20 MHz)\n* `6g-175-6825-160` - 175 (6825/160 MHz)\n* `6g-177-6835-20` - 177 (6835/20 MHz)\n* `6g-179-6845-40` - 179 (6845/40 MHz)\n* `6g-181-6855-20` - 181 (6855/20 MHz)\n* `6g-183-6865-80` - 183 (6865/80 MHz)\n* `6g-185-6875-20` - 185 (6875/20 MHz)\n* `6g-187-6885-40` - 187 (6885/40 MHz)\n* `6g-189-6895-20` - 189 (6895/20 MHz)\n* `6g-193-6915-20` - 193 (6915/20 MHz)\n* `6g-195-6925-40` - 195 (6925/40 MHz)\n* `6g-197-6935-20` - 197 (6935/20 MHz)\n* `6g-199-6945-80` - 199 (6945/80 MHz)\n* `6g-201-6955-20` - 201 (6955/20 MHz)\n* `6g-203-6965-40` - 203 (6965/40 MHz)\n* `6g-205-6975-20` - 205 (6975/20 MHz)\n* `6g-207-6985-160` - 207 (6985/160 MHz)\n* `6g-209-6995-20` - 209 (6995/20 MHz)\n* `6g-211-7005-40` - 211 (7005/40 MHz)\n* `6g-213-7015-20` - 213 (7015/20 MHz)\n* `6g-215-7025-80` - 215 (7025/80 MHz)\n* `6g-217-7035-20` - 217 (7035/20 MHz)\n* `6g-219-7045-40` - 219 (7045/40 MHz)\n* `6g-221-7055-20` - 221 (7055/20 MHz)\n* `6g-225-7075-20` - 225 (7075/20 MHz)\n* `6g-227-7085-40` - 227 (7085/40 MHz)\n* `6g-229-7095-20` - 229 (7095/20 MHz)\n* `6g-233-7115-20` - 233 (7115/20 MHz)\n* `60g-1-58320-2160` - 1 (58.32/2.16 GHz)\n* `60g-2-60480-2160` - 2 (60.48/2.16 GHz)\n* `60g-3-62640-2160` - 3 (62.64/2.16 GHz)\n* `60g-4-64800-2160` - 4 (64.80/2.16 GHz)\n* `60g-5-66960-2160` - 5 (66.96/2.16 GHz)\n* `60g-6-69120-2160` - 6 (69.12/2.16 GHz)\n* `60g-9-59400-4320` - 9 (59.40/4.32 GHz)\n* `60g-10-61560-4320` - 10 (61.56/4.32 GHz)\n* `60g-11-63720-4320` - 11 (63.72/4.32 GHz)\n* `60g-12-65880-4320` - 12 (65.88/4.32 GHz)\n* `60g-13-68040-4320` - 13 (68.04/4.32 GHz)\n* `60g-17-60480-6480` - 17 (60.48/6.48 GHz)\n* `60g-18-62640-6480` - 18 (62.64/6.48 GHz)\n* `60g-19-64800-6480` - 19 (64.80/6.48 GHz)\n* `60g-20-66960-6480` - 20 (66.96/6.48 GHz)\n* `60g-25-61560-6480` - 25 (61.56/8.64 GHz)\n* `60g-26-63720-6480` - 26 (63.72/8.64 GHz)\n* `60g-27-65880-6480` - 27 (65.88/8.64 GHz)", + "x-spec-enum-id": "70cf66176c475063" + }, + "poe_mode": { + "enum": [ + "pd", + "pse", + "" + ], + "type": "string", + "description": "* `pd` - PD\n* `pse` - PSE", + "x-spec-enum-id": "2f2fe6dcdc7772bd" + }, + "poe_type": { + "enum": [ + "type1-ieee802.3af", + "type2-ieee802.3at", + "type3-ieee802.3bt", + "type4-ieee802.3bt", + "passive-24v-2pair", + "passive-24v-4pair", + "passive-48v-2pair", + "passive-48v-4pair", + "" + ], + "type": "string", + "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)", + "x-spec-enum-id": "5473d57885f237ab" + }, + "rf_channel_frequency": { + "type": "number", + "format": "double", + "maximum": 100000, + "minimum": -100000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true, + "title": "Channel frequency (MHz)", + "description": "Populated by selected channel (if set)" + }, + "rf_channel_width": { + "type": "number", + "format": "double", + "maximum": 10000, + "minimum": -10000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true, + "title": "Channel width (MHz)", + "description": "Populated by selected channel (if set)" + }, + "tx_power": { + "type": "integer", + "maximum": 127, + "minimum": -40, + "nullable": true, + "title": "Transmit power (dBm)" + }, + "untagged_vlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tagged_vlans": { + "type": "array", + "items": { + "type": "integer" + } + }, + "qinq_svlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vlan_translation_policy": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "wireless_lans": { + "type": "array", + "items": { + "type": "integer" + } + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkInterfaceTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "virtual", + "bridge", + "lag", + "100base-fx", + "100base-lfx", + "100base-tx", + "100base-t1", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cwdm", + "1000base-cx", + "1000base-dwdm", + "1000base-ex", + "1000base-lsx", + "1000base-lx", + "1000base-lx10", + "1000base-sx", + "1000base-t", + "1000base-tx", + "1000base-zx", + "2.5gbase-t", + "5gbase-t", + "10gbase-br-d", + "10gbase-br-u", + "10gbase-cu", + "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "40gbase-sr4-bd", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-cwdm4", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", + "1.6tbase-cr8", + "1.6tbase-dr8", + "1.6tbase-dr8-2", + "100base-x-sfp", + "1000base-x-gbic", + "1000base-x-sfp", + "2.5gbase-x-sfp", + "10gbase-x-sfpp", + "10gbase-x-xenpak", + "10gbase-x-xfp", + "10gbase-x-x2", + "25gbase-x-sfp28", + "40gbase-x-qsfpp", + "50gbase-x-sfp28", + "50gbase-x-sfp56", + "100gbase-x-cfp", + "100gbase-x-cfp2", + "100gbase-x-cfp4", + "100gbase-x-cxp", + "100gbase-x-cpak", + "100gbase-x-dsfp", + "100gbase-x-qsfp28", + "100gbase-x-qsfpdd", + "100gbase-x-sfpdd", + "200gbase-x-cfp2", + "200gbase-x-qsfp56", + "200gbase-x-qsfpdd", + "400gbase-x-qsfp112", + "400gbase-x-qsfpdd", + "400gbase-x-cdfp", + "400gbase-x-cfp2", + "400gbase-x-cfp8", + "400gbase-x-osfp", + "400gbase-x-osfp-rhs", + "800gbase-x-osfp", + "800gbase-x-qsfpdd", + "1.6tbase-x-osfp1600", + "1.6tbase-x-osfp1600-rhs", + "1.6tbase-x-qsfpdd1600", + "1000base-kx", + "2.5gbase-kx", + "5gbase-kr", + "10gbase-kr", + "10gbase-kx4", + "25gbase-kr", + "40gbase-kr4", + "50gbase-kr", + "100gbase-kp4", + "100gbase-kr2", + "100gbase-kr4", + "1.6tbase-kr8", + "ieee802.11a", + "ieee802.11g", + "ieee802.11n", + "ieee802.11ac", + "ieee802.11ad", + "ieee802.11ax", + "ieee802.11ay", + "ieee802.11be", + "ieee802.15.1", + "ieee802.15.4", + "other-wireless", + "gsm", + "cdma", + "lte", + "4g", + "5g", + "sonet-oc3", + "sonet-oc12", + "sonet-oc48", + "sonet-oc192", + "sonet-oc768", + "sonet-oc1920", + "sonet-oc3840", + "1gfc-sfp", + "2gfc-sfp", + "4gfc-sfp", + "8gfc-sfpp", + "16gfc-sfpp", + "32gfc-sfp28", + "32gfc-sfpp", + "64gfc-qsfpp", + "64gfc-sfpdd", + "64gfc-sfpp", + "128gfc-qsfp28", + "infiniband-sdr", + "infiniband-ddr", + "infiniband-qdr", + "infiniband-fdr10", + "infiniband-fdr", + "infiniband-edr", + "infiniband-hdr", + "infiniband-ndr", + "infiniband-xdr", + "t1", + "e1", + "t3", + "e3", + "xdsl", + "docsis", + "moca", + "bpon", + "epon", + "10g-epon", + "gpon", + "xg-pon", + "xgs-pon", + "ng-pon2", + "25g-pon", + "50g-pon", + "cisco-stackwise", + "cisco-stackwise-plus", + "cisco-flexstack", + "cisco-flexstack-plus", + "cisco-stackwise-80", + "cisco-stackwise-160", + "cisco-stackwise-320", + "cisco-stackwise-480", + "cisco-stackwise-1t", + "juniper-vcp", + "extreme-summitstack", + "extreme-summitstack-128", + "extreme-summitstack-256", + "extreme-summitstack-512", + "other" + ], + "type": "string", + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cu` - 10GBASE-CU (10GE DAC Passive Twinax)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `40gbase-sr4-bd` - 40GBASE-SR4 (40GE BiDi)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `1.6tbase-cr8` - 1.6TBASE-CR8 (1.6TE)\n* `1.6tbase-dr8` - 1.6TBASE-DR8 (1.6TE)\n* `1.6tbase-dr8-2` - 1.6TBASE-DR8-2 (1.6TE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `2.5gbase-x-sfp` - SFP (2.5GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1.6tbase-x-osfp1600` - OSFP1600 (1.6TE)\n* `1.6tbase-x-osfp1600-rhs` - OSFP1600-RHS (1.6TE)\n* `1.6tbase-x-qsfpdd1600` - QSFP-DD1600 (1.6TE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `1.6tbase-kr8` - 1.6TBASE-KR8 (1.6TE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "b067eb1f050c6ae9" + }, + "enabled": { + "type": "boolean" + }, + "mgmt_only": { + "type": "boolean", + "title": "Management only" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "bridge": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedInterfaceTemplateRequest" + } + ], + "nullable": true + }, + "poe_mode": { + "enum": [ + "pd", + "pse", + "", + null + ], + "type": "string", + "description": "* `pd` - PD\n* `pse` - PSE", + "x-spec-enum-id": "2f2fe6dcdc7772bd", + "nullable": true + }, + "poe_type": { + "enum": [ + "type1-ieee802.3af", + "type2-ieee802.3at", + "type3-ieee802.3bt", + "type4-ieee802.3bt", + "passive-24v-2pair", + "passive-24v-4pair", + "passive-48v-2pair", + "passive-48v-4pair", + "", + null + ], + "type": "string", + "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)", + "x-spec-enum-id": "5473d57885f237ab", + "nullable": true + }, + "rf_role": { + "enum": [ + "ap", + "station", + "", + null + ], + "type": "string", + "description": "* `ap` - Access point\n* `station` - Station", + "x-spec-enum-id": "d2772dbea88b0fb1", + "nullable": true + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkInventoryItemRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "parent": { + "type": "integer", + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "staged", + "failed", + "decommissioning" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "545817eb4c4f2ae4" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefInventoryItemRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "part_id": { + "type": "string", + "description": "Manufacturer-assigned part identifier", + "maxLength": 50 + }, + "serial": { + "type": "string", + "title": "Serial number", + "maxLength": 50 + }, + "asset_tag": { + "type": "string", + "nullable": true, + "description": "A unique tag used to identify this item", + "maxLength": 50 + }, + "discovered": { + "type": "boolean", + "description": "This item was automatically discovered" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "component_type": { + "type": "string", + "nullable": true + }, + "component_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkInventoryItemRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "minLength": 1, + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkInventoryItemTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ] + }, + "parent": { + "type": "integer", + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefInventoryItemRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "part_id": { + "type": "string", + "description": "Manufacturer-assigned part identifier", + "maxLength": 50 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "component_type": { + "type": "string", + "nullable": true + }, + "component_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkJournalEntryRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "assigned_object_type": { + "type": "string" + }, + "assigned_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "created_by": { + "type": "integer", + "nullable": true + }, + "kind": { + "enum": [ + "info", + "success", + "warning", + "danger" + ], + "type": "string", + "description": "* `info` - Info\n* `success` - Success\n* `warning` - Warning\n* `danger` - Danger", + "x-spec-enum-id": "6f65abe0aab2c78c" + }, + "comments": { + "type": "string", + "minLength": 1 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkL2VPNRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "identifier": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "format": "int64", + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "type": { + "enum": [ + "vpws", + "vpls", + "vxlan", + "vxlan-evpn", + "mpls-evpn", + "pbb-evpn", + "evpn-vpws", + "epl", + "evpl", + "ep-lan", + "evp-lan", + "ep-tree", + "evp-tree", + "spb" + ], + "type": "string", + "description": "* `vpws` - VPWS\n* `vpls` - VPLS\n* `vxlan` - VXLAN\n* `vxlan-evpn` - VXLAN-EVPN\n* `mpls-evpn` - MPLS EVPN\n* `pbb-evpn` - PBB EVPN\n* `evpn-vpws` - EVPN VPWS\n* `epl` - EPL\n* `evpl` - EVPL\n* `ep-lan` - Ethernet Private LAN\n* `evp-lan` - Ethernet Virtual Private LAN\n* `ep-tree` - Ethernet Private Tree\n* `evp-tree` - Ethernet Virtual Private Tree\n* `spb` - SPB", + "x-spec-enum-id": "0a46f8056d717efc" + }, + "status": { + "enum": [ + "active", + "planned", + "decommissioning" + ], + "type": "string", + "description": "* `active` - Active\n* `planned` - Planned\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "8b9dc8efc7c3d5b0" + }, + "import_targets": { + "type": "array", + "items": { + "type": "integer" + } + }, + "export_targets": { + "type": "array", + "items": { + "type": "integer" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkL2VPNTerminationRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "l2vpn": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefL2VPNRequest" + } + ] + }, + "assigned_object_type": { + "type": "string" + }, + "assigned_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkLocationRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ] + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedLocationRequest" + } + ], + "nullable": true + }, + "status": { + "enum": [ + "planned", + "staging", + "active", + "decommissioning", + "retired" + ], + "type": "string", + "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired", + "x-spec-enum-id": "1cf60831fbb35e7f" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "facility": { + "type": "string", + "description": "Local facility ID or description", + "maxLength": 50 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkMACAddressRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "mac_address": { + "type": "string", + "minLength": 1 + }, + "assigned_object_type": { + "type": "string", + "nullable": true + }, + "assigned_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkManufacturerRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkModuleBayRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "position": { + "type": "string", + "description": "Identifier to reference when renaming installed components", + "maxLength": 30 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "installed_module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkModuleBayTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "position": { + "type": "string", + "description": "Identifier to reference when renaming installed components", + "maxLength": 30 + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkModuleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module_bay": { + "$ref": "#/components/schemas/NestedModuleBayRequest" + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ] + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "staged", + "failed", + "decommissioning" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "545817eb4c4f2ae4" + }, + "serial": { + "type": "string", + "title": "Serial number", + "maxLength": 50 + }, + "asset_tag": { + "type": "string", + "nullable": true, + "description": "A unique tag used to identify this device", + "maxLength": 50 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "replicate_components": { + "type": "boolean", + "writeOnly": true, + "default": true, + "description": "Automatically populate components associated with this module type (default: true)" + }, + "adopt_components": { + "type": "boolean", + "writeOnly": true, + "default": false, + "description": "Adopt already existing components" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkModuleTypeProfileRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "schema": { + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkModuleTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "profile": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeProfileRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ] + }, + "model": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "part_number": { + "type": "string", + "description": "Discrete part number (optional)", + "maxLength": 50 + }, + "airflow": { + "enum": [ + "front-to-rear", + "rear-to-front", + "left-to-right", + "right-to-left", + "side-to-rear", + "passive", + "", + null + ], + "type": "string", + "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `passive` - Passive", + "x-spec-enum-id": "5ad4e700c656b09d", + "nullable": true + }, + "weight": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "weight_unit": { + "enum": [ + "kg", + "g", + "lb", + "oz", + "", + null + ], + "type": "string", + "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces", + "x-spec-enum-id": "2235ce3f404afbc0", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "attributes": { + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkNotificationGroupRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "users": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkNotificationRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + }, + "read": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "event_type": { + "enum": [ + "object_created", + "object_updated", + "object_deleted", + "job_started", + "job_completed", + "job_failed", + "job_errored" + ], + "type": "string", + "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored", + "x-spec-enum-id": "01e557313a5c7bd2", + "title": "Event" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkObjectPermissionRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "enabled": { + "type": "boolean" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "actions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 30 + }, + "description": "The list of actions granted by this permission" + }, + "constraints": { + "nullable": true, + "description": "Queryset filter matching the applicable objects of the selected type(s)" + }, + "groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "users": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkOwnerGroupRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkOwnerRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "user_groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "users": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkPlatformRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedPlatformRequest" + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "config_template": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkPowerFeedRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "power_panel": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefPowerPanelRequest" + } + ] + }, + "rack": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "failed" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `failed` - Failed", + "x-spec-enum-id": "ec530572dc778583" + }, + "type": { + "enum": [ + "primary", + "redundant" + ], + "type": "string", + "description": "* `primary` - Primary\n* `redundant` - Redundant", + "x-spec-enum-id": "093a164236819eb8" + }, + "supply": { + "enum": [ + "ac", + "dc" + ], + "type": "string", + "description": "* `ac` - AC\n* `dc` - DC", + "x-spec-enum-id": "1b6d99616ca6412b" + }, + "phase": { + "enum": [ + "single-phase", + "three-phase" + ], + "type": "string", + "description": "* `single-phase` - Single phase\n* `three-phase` - Three-phase", + "x-spec-enum-id": "994bc0696f4df57f" + }, + "voltage": { + "type": "integer", + "maximum": 32767, + "minimum": -32768 + }, + "amperage": { + "type": "integer", + "maximum": 32767, + "minimum": 1 + }, + "max_utilization": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Maximum permissible draw (percentage)" + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkPowerOutletRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "iec-60320-c5", + "iec-60320-c7", + "iec-60320-c13", + "iec-60320-c15", + "iec-60320-c17", + "iec-60320-c19", + "iec-60320-c21", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15r", + "nema-5-15r", + "nema-5-20r", + "nema-5-30r", + "nema-5-50r", + "nema-6-15r", + "nema-6-20r", + "nema-6-30r", + "nema-6-50r", + "nema-10-30r", + "nema-10-50r", + "nema-14-20r", + "nema-14-30r", + "nema-14-50r", + "nema-14-60r", + "nema-15-15r", + "nema-15-20r", + "nema-15-30r", + "nema-15-50r", + "nema-15-60r", + "nema-l1-15r", + "nema-l5-15r", + "nema-l5-20r", + "nema-l5-30r", + "nema-l5-50r", + "nema-l6-15r", + "nema-l6-20r", + "nema-l6-30r", + "nema-l6-50r", + "nema-l10-30r", + "nema-l14-20r", + "nema-l14-30r", + "nema-l14-50r", + "nema-l14-60r", + "nema-l15-20r", + "nema-l15-30r", + "nema-l15-50r", + "nema-l15-60r", + "nema-l21-20r", + "nema-l21-30r", + "nema-l22-20r", + "nema-l22-30r", + "CS6360C", + "CS6364C", + "CS8164C", + "CS8264C", + "CS8364C", + "CS8464C", + "ita-e", + "ita-f", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "ita-multistandard", + "usb-a", + "usb-micro-b", + "usb-c", + "molex-micro-fit-1x2", + "molex-micro-fit-2x2", + "molex-micro-fit-2x3", + "molex-micro-fit-2x4", + "dc-terminal", + "eaton-c39", + "hdot-cx", + "saf-d-grid", + "neutrik-powercon-20a", + "neutrik-powercon-32a", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other", + "", + null + ], + "type": "string", + "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other", + "x-spec-enum-id": "db3e4eb2b93615f8", + "nullable": true + }, + "status": { + "enum": [ + "enabled", + "disabled", + "faulty" + ], + "type": "string", + "description": "* `enabled` - Enabled\n* `disabled` - Disabled\n* `faulty` - Faulty", + "x-spec-enum-id": "d60dce16858f3c69" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "power_port": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPowerPortRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "feed_leg": { + "enum": [ + "A", + "B", + "C", + "", + null + ], + "type": "string", + "description": "* `A` - A\n* `B` - B\n* `C` - C", + "x-spec-enum-id": "a4902339df0b7c06", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkPowerOutletTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "iec-60320-c5", + "iec-60320-c7", + "iec-60320-c13", + "iec-60320-c15", + "iec-60320-c17", + "iec-60320-c19", + "iec-60320-c21", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15r", + "nema-5-15r", + "nema-5-20r", + "nema-5-30r", + "nema-5-50r", + "nema-6-15r", + "nema-6-20r", + "nema-6-30r", + "nema-6-50r", + "nema-10-30r", + "nema-10-50r", + "nema-14-20r", + "nema-14-30r", + "nema-14-50r", + "nema-14-60r", + "nema-15-15r", + "nema-15-20r", + "nema-15-30r", + "nema-15-50r", + "nema-15-60r", + "nema-l1-15r", + "nema-l5-15r", + "nema-l5-20r", + "nema-l5-30r", + "nema-l5-50r", + "nema-l6-15r", + "nema-l6-20r", + "nema-l6-30r", + "nema-l6-50r", + "nema-l10-30r", + "nema-l14-20r", + "nema-l14-30r", + "nema-l14-50r", + "nema-l14-60r", + "nema-l15-20r", + "nema-l15-30r", + "nema-l15-50r", + "nema-l15-60r", + "nema-l21-20r", + "nema-l21-30r", + "nema-l22-20r", + "nema-l22-30r", + "CS6360C", + "CS6364C", + "CS8164C", + "CS8264C", + "CS8364C", + "CS8464C", + "ita-e", + "ita-f", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "ita-multistandard", + "usb-a", + "usb-micro-b", + "usb-c", + "molex-micro-fit-1x2", + "molex-micro-fit-2x2", + "molex-micro-fit-2x3", + "molex-micro-fit-2x4", + "dc-terminal", + "eaton-c39", + "hdot-cx", + "saf-d-grid", + "neutrik-powercon-20a", + "neutrik-powercon-32a", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other", + "", + null + ], + "type": "string", + "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other", + "x-spec-enum-id": "db3e4eb2b93615f8", + "nullable": true + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "power_port": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPowerPortTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "feed_leg": { + "enum": [ + "A", + "B", + "C", + "", + null + ], + "type": "string", + "description": "* `A` - A\n* `B` - B\n* `C` - C", + "x-spec-enum-id": "a4902339df0b7c06", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkPowerPanelRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ] + }, + "location": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefLocationRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkPowerPortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "iec-60320-c6", + "iec-60320-c8", + "iec-60320-c14", + "iec-60320-c16", + "iec-60320-c18", + "iec-60320-c20", + "iec-60320-c22", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15p", + "nema-5-15p", + "nema-5-20p", + "nema-5-30p", + "nema-5-50p", + "nema-6-15p", + "nema-6-20p", + "nema-6-30p", + "nema-6-50p", + "nema-10-30p", + "nema-10-50p", + "nema-14-20p", + "nema-14-30p", + "nema-14-50p", + "nema-14-60p", + "nema-15-15p", + "nema-15-20p", + "nema-15-30p", + "nema-15-50p", + "nema-15-60p", + "nema-l1-15p", + "nema-l5-15p", + "nema-l5-20p", + "nema-l5-30p", + "nema-l5-50p", + "nema-l6-15p", + "nema-l6-20p", + "nema-l6-30p", + "nema-l6-50p", + "nema-l10-30p", + "nema-l14-20p", + "nema-l14-30p", + "nema-l14-50p", + "nema-l14-60p", + "nema-l15-20p", + "nema-l15-30p", + "nema-l15-50p", + "nema-l15-60p", + "nema-l21-20p", + "nema-l21-30p", + "nema-l22-20p", + "nema-l22-30p", + "cs6361c", + "cs6365c", + "cs8165c", + "cs8265c", + "cs8365c", + "cs8465c", + "ita-c", + "ita-e", + "ita-f", + "ita-ef", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "usb-3-b", + "usb-3-micro-b", + "molex-micro-fit-1x2", + "molex-micro-fit-2x2", + "molex-micro-fit-2x3", + "molex-micro-fit-2x4", + "dc-terminal", + "saf-d-grid", + "neutrik-powercon-20", + "neutrik-powercon-32", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other", + "", + null + ], + "type": "string", + "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other", + "x-spec-enum-id": "aadcbe6ca854c1ed", + "nullable": true + }, + "maximum_draw": { + "type": "integer", + "maximum": 2147483647, + "minimum": 1, + "nullable": true, + "description": "Maximum power draw (watts)" + }, + "allocated_draw": { + "type": "integer", + "maximum": 2147483647, + "minimum": 1, + "nullable": true, + "description": "Allocated power draw (watts)" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkPowerPortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "iec-60320-c6", + "iec-60320-c8", + "iec-60320-c14", + "iec-60320-c16", + "iec-60320-c18", + "iec-60320-c20", + "iec-60320-c22", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15p", + "nema-5-15p", + "nema-5-20p", + "nema-5-30p", + "nema-5-50p", + "nema-6-15p", + "nema-6-20p", + "nema-6-30p", + "nema-6-50p", + "nema-10-30p", + "nema-10-50p", + "nema-14-20p", + "nema-14-30p", + "nema-14-50p", + "nema-14-60p", + "nema-15-15p", + "nema-15-20p", + "nema-15-30p", + "nema-15-50p", + "nema-15-60p", + "nema-l1-15p", + "nema-l5-15p", + "nema-l5-20p", + "nema-l5-30p", + "nema-l5-50p", + "nema-l6-15p", + "nema-l6-20p", + "nema-l6-30p", + "nema-l6-50p", + "nema-l10-30p", + "nema-l14-20p", + "nema-l14-30p", + "nema-l14-50p", + "nema-l14-60p", + "nema-l15-20p", + "nema-l15-30p", + "nema-l15-50p", + "nema-l15-60p", + "nema-l21-20p", + "nema-l21-30p", + "nema-l22-20p", + "nema-l22-30p", + "cs6361c", + "cs6365c", + "cs8165c", + "cs8265c", + "cs8365c", + "cs8465c", + "ita-c", + "ita-e", + "ita-f", + "ita-ef", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "usb-3-b", + "usb-3-micro-b", + "molex-micro-fit-1x2", + "molex-micro-fit-2x2", + "molex-micro-fit-2x3", + "molex-micro-fit-2x4", + "dc-terminal", + "saf-d-grid", + "neutrik-powercon-20", + "neutrik-powercon-32", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other", + "", + null + ], + "type": "string", + "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other", + "x-spec-enum-id": "aadcbe6ca854c1ed", + "nullable": true + }, + "maximum_draw": { + "type": "integer", + "maximum": 2147483647, + "minimum": 1, + "nullable": true, + "description": "Maximum power draw (watts)" + }, + "allocated_draw": { + "type": "integer", + "maximum": 2147483647, + "minimum": 1, + "nullable": true, + "description": "Allocated power draw (watts)" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkPrefixRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "prefix": { + "type": "string", + "minLength": 1 + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "scope_type": { + "type": "string", + "nullable": true + }, + "scope_id": { + "type": "integer", + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "container", + "active", + "reserved", + "deprecated" + ], + "type": "string", + "description": "* `container` - Container\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated", + "x-spec-enum-id": "026173ce39f2ee63" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "is_pool": { + "type": "boolean", + "title": "Is a pool", + "description": "All IP addresses within this prefix are considered usable" + }, + "mark_utilized": { + "type": "boolean", + "description": "Treat as fully utilized" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkProviderAccountRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "provider": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefProviderRequest" + } + ] + }, + "name": { + "type": "string", + "default": "", + "maxLength": 100 + }, + "account": { + "type": "string", + "minLength": 1, + "title": "Account ID", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkProviderNetworkRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "provider": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefProviderRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "service_id": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkProviderRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Full name of the provider", + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "accounts": { + "type": "array", + "items": { + "type": "integer" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "asns": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRIRRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "is_private": { + "type": "boolean", + "title": "Private", + "description": "IP space managed by this RIR is considered private" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRackGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRackRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "facility_id": { + "type": "string", + "nullable": true, + "maxLength": 50 + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ] + }, + "location": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefLocationRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "reserved", + "available", + "planned", + "active", + "deprecated" + ], + "type": "string", + "description": "* `reserved` - Reserved\n* `available` - Available\n* `planned` - Planned\n* `active` - Active\n* `deprecated` - Deprecated", + "x-spec-enum-id": "76eea4eef8804bcb" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "serial": { + "type": "string", + "title": "Serial number", + "maxLength": 50 + }, + "asset_tag": { + "type": "string", + "nullable": true, + "description": "A unique tag used to identify this rack", + "maxLength": 50 + }, + "rack_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRackTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "form_factor": { + "enum": [ + "2-post-frame", + "4-post-frame", + "4-post-cabinet", + "wall-frame", + "wall-frame-vertical", + "wall-cabinet", + "wall-cabinet-vertical", + "", + null + ], + "type": "string", + "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)", + "x-spec-enum-id": "8a902fde21d48841", + "nullable": true + }, + "width": { + "enum": [ + 10, + 19, + 21, + 23 + ], + "type": "integer", + "description": "* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches", + "x-spec-enum-id": "9b322795f297a9c3" + }, + "u_height": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "title": "Height (U)", + "description": "Height in rack units" + }, + "starting_unit": { + "type": "integer", + "maximum": 32767, + "minimum": 1, + "description": "Starting unit for rack" + }, + "weight": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "max_weight": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "description": "Maximum load capacity for the rack" + }, + "weight_unit": { + "enum": [ + "kg", + "g", + "lb", + "oz", + "", + null + ], + "type": "string", + "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces", + "x-spec-enum-id": "2235ce3f404afbc0", + "nullable": true + }, + "desc_units": { + "type": "boolean", + "title": "Descending units", + "description": "Units are numbered top-to-bottom" + }, + "outer_width": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (width)" + }, + "outer_height": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (height)" + }, + "outer_depth": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (depth)" + }, + "outer_unit": { + "enum": [ + "mm", + "in", + "", + null + ], + "type": "string", + "description": "* `mm` - Millimeters\n* `in` - Inches", + "x-spec-enum-id": "3d701848b66312c3", + "nullable": true + }, + "mounting_depth": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails." + }, + "airflow": { + "enum": [ + "front-to-rear", + "rear-to-front", + "" + ], + "type": "string", + "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front", + "x-spec-enum-id": "a784734d07ef1b3c" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRackReservationRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "rack": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefRackRequest" + } + ] + }, + "units": { + "type": "array", + "items": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + } + }, + "status": { + "enum": [ + "pending", + "active", + "stale" + ], + "type": "string", + "description": "* `pending` - Pending\n* `active` - Active\n* `stale` - Stale", + "x-spec-enum-id": "ed6038a4deee151c" + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRackRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "minLength": 1, + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRackTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "manufacturer": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefManufacturerRequest" + } + ] + }, + "model": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "form_factor": { + "enum": [ + "2-post-frame", + "4-post-frame", + "4-post-cabinet", + "wall-frame", + "wall-frame-vertical", + "wall-cabinet", + "wall-cabinet-vertical", + "", + null + ], + "type": "string", + "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)", + "x-spec-enum-id": "8a902fde21d48841", + "nullable": true + }, + "width": { + "enum": [ + 10, + 19, + 21, + 23 + ], + "type": "integer", + "description": "* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches", + "x-spec-enum-id": "9b322795f297a9c3" + }, + "u_height": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "title": "Height (U)", + "description": "Height in rack units" + }, + "starting_unit": { + "type": "integer", + "maximum": 32767, + "minimum": 1, + "description": "Starting unit for rack" + }, + "desc_units": { + "type": "boolean", + "title": "Descending units", + "description": "Units are numbered top-to-bottom" + }, + "outer_width": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (width)" + }, + "outer_height": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (height)" + }, + "outer_depth": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Outer dimension of rack (depth)" + }, + "outer_unit": { + "enum": [ + "mm", + "in", + "", + null + ], + "type": "string", + "description": "* `mm` - Millimeters\n* `in` - Inches", + "x-spec-enum-id": "3d701848b66312c3", + "nullable": true + }, + "weight": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "max_weight": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "description": "Maximum load capacity for the rack" + }, + "weight_unit": { + "enum": [ + "kg", + "g", + "lb", + "oz", + "", + null + ], + "type": "string", + "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces", + "x-spec-enum-id": "2235ce3f404afbc0", + "nullable": true + }, + "mounting_depth": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails." + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRearPortRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "module": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "fc-pc", + "fc-upc", + "fc-apc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ], + "type": "string", + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "positions": { + "type": "integer", + "maximum": 1024, + "minimum": 1 + }, + "front_ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RearPortMappingRequest" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mark_connected": { + "type": "boolean", + "description": "Treat as if a cable is connected" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRearPortTemplateRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "device_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "module_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefModuleTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "name": { + "type": "string", + "minLength": 1, + "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.", + "maxLength": 64 + }, + "label": { + "type": "string", + "description": "Physical label", + "maxLength": 64 + }, + "type": { + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "fc-pc", + "fc-upc", + "fc-apc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ], + "type": "string", + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "positions": { + "type": "integer", + "maximum": 1024, + "minimum": 1 + }, + "front_ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RearPortTemplateMappingRequest" + } + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRegionRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedRegionRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRoleRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkRouteTargetRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Route target value (formatted in accordance with RFC 4360)", + "maxLength": 21 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkSavedFilterRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "user": { + "type": "integer", + "nullable": true + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "enabled": { + "type": "boolean" + }, + "shared": { + "type": "boolean" + }, + "parameters": {}, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkServiceRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "parent_object_type": { + "type": "string" + }, + "parent_object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "protocol": { + "enum": [ + "tcp", + "udp", + "sctp" + ], + "type": "string", + "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP", + "x-spec-enum-id": "e4b15bec749a2a32" + }, + "ports": { + "type": "array", + "items": { + "type": "integer", + "maximum": 65535, + "minimum": 1 + }, + "title": "Port numbers" + }, + "ipaddresses": { + "type": "array", + "items": { + "type": "integer" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkServiceTemplateRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "protocol": { + "enum": [ + "tcp", + "udp", + "sctp" + ], + "type": "string", + "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP", + "x-spec-enum-id": "e4b15bec749a2a32" + }, + "ports": { + "type": "array", + "items": { + "type": "integer", + "maximum": 65535, + "minimum": 1 + }, + "title": "Port numbers" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkSiteGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedSiteGroupRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkSiteRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Full name of the site", + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "status": { + "enum": [ + "planned", + "staging", + "active", + "decommissioning", + "retired" + ], + "type": "string", + "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired", + "x-spec-enum-id": "1cf60831fbb35e7f" + }, + "region": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRegionRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefSiteGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "facility": { + "type": "string", + "description": "Local facility ID or description", + "maxLength": 50 + }, + "time_zone": { + "type": "string", + "nullable": true, + "minLength": 1 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "physical_address": { + "type": "string", + "description": "Physical location of the building", + "maxLength": 200 + }, + "shipping_address": { + "type": "string", + "description": "If different from the physical address", + "maxLength": 200 + }, + "latitude": { + "type": "number", + "format": "double", + "maximum": 90.0, + "minimum": -90.0, + "nullable": true, + "description": "GPS coordinate in decimal format (xx.yyyyyy)" + }, + "longitude": { + "type": "number", + "format": "double", + "maximum": 180.0, + "minimum": -180.0, + "nullable": true, + "description": "GPS coordinate in decimal format (xx.yyyyyy)" + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "asns": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkSubscriptionRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "object_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64" + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkTableConfigRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "object_type": { + "type": "string" + }, + "table": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "user": { + "type": "integer", + "nullable": true + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "enabled": { + "type": "boolean" + }, + "shared": { + "type": "boolean" + }, + "columns": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 100 + } + }, + "ordering": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "nullable": true + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkTagRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "pattern": "^[-\\w]+$", + "maxLength": 100 + }, + "color": { + "type": "string", + "minLength": 1, + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "weight": { + "type": "integer", + "maximum": 32767, + "minimum": 0 + }, + "object_types": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkTenantGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedTenantGroupRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkTenantRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkTokenRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "version": { + "enum": [ + 1, + 2 + ], + "type": "integer", + "description": "* `1` - v1\n* `2` - v2", + "x-spec-enum-id": "b5df70f0bffd12cb", + "minimum": 0, + "maximum": 32767 + }, + "user": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefUserRequest" + } + ] + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "expires": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_used": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "enabled": { + "type": "boolean", + "description": "Disable to temporarily revoke this token without deleting it." + }, + "write_enabled": { + "type": "boolean", + "description": "Permit create/update/delete operations using this token" + }, + "pepper_id": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true, + "description": "ID of the cryptographic pepper used to hash the token (v2 only)" + }, + "token": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkTunnelGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkTunnelRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "status": { + "enum": [ + "planned", + "active", + "disabled" + ], + "type": "string", + "description": "* `planned` - Planned\n* `active` - Active\n* `disabled` - Disabled", + "x-spec-enum-id": "2431ef62c418f485" + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTunnelGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "encapsulation": { + "enum": [ + "ipsec-transport", + "ipsec-tunnel", + "ip-ip", + "gre", + "wireguard", + "openvpn", + "l2tp", + "pptp" + ], + "type": "string", + "description": "* `ipsec-transport` - IPsec - Transport\n* `ipsec-tunnel` - IPsec - Tunnel\n* `ip-ip` - IP-in-IP\n* `gre` - GRE\n* `wireguard` - WireGuard\n* `openvpn` - OpenVPN\n* `l2tp` - L2TP\n* `pptp` - PPTP", + "x-spec-enum-id": "4f3254459f0e94f0" + }, + "ipsec_profile": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPSecProfileRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tunnel_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkTunnelTerminationRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "tunnel": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefTunnelRequest" + } + ] + }, + "role": { + "enum": [ + "peer", + "hub", + "spoke" + ], + "type": "string", + "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke", + "x-spec-enum-id": "0b3bfadcebd86b58" + }, + "termination_type": { + "type": "string" + }, + "termination_id": { + "type": "integer", + "maximum": 9223372036854775807, + "minimum": 0, + "format": "int64", + "nullable": true + }, + "outside_ip": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkUserRequest": { + "type": "object", + "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)", + "properties": { + "id": { + "type": "integer" + }, + "username": { + "type": "string", + "minLength": 1, + "description": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + "pattern": "^[\\w.@+-]+$", + "maxLength": 150 + }, + "password": { + "type": "string", + "writeOnly": true, + "minLength": 1, + "maxLength": 128 + }, + "first_name": { + "type": "string", + "maxLength": 150 + }, + "last_name": { + "type": "string", + "maxLength": 150 + }, + "email": { + "type": "string", + "format": "email", + "title": "Email address", + "maxLength": 254 + }, + "is_active": { + "type": "boolean", + "title": "Active", + "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts." + }, + "date_joined": { + "type": "string", + "format": "date-time" + }, + "last_login": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "groups": { + "type": "array", + "items": { + "type": "integer" + } + }, + "permissions": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVLANGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "scope_type": { + "type": "string", + "nullable": true + }, + "scope_id": { + "type": "integer", + "nullable": true + }, + "vid_ranges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IntegerRangeRequest" + } + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVLANRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vid": { + "type": "integer", + "maximum": 4094, + "minimum": 1, + "title": "VLAN ID", + "description": "Numeric VLAN ID (1-4094)" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "reserved", + "deprecated" + ], + "type": "string", + "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated", + "x-spec-enum-id": "ca933c38b935e547" + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "qinq_role": { + "enum": [ + "svlan", + "cvlan", + null + ], + "type": "string", + "description": "* `svlan` - Service\n* `cvlan` - Customer", + "x-spec-enum-id": "fa0abd59fb1a7312", + "nullable": true + }, + "qinq_svlan": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedVLANRequest" + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVLANTranslationPolicyRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVLANTranslationRuleRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "policy": { + "type": "integer" + }, + "local_vid": { + "type": "integer", + "maximum": 4094, + "minimum": 1, + "title": "Local VLAN ID", + "description": "Numeric VLAN ID (1-4094)" + }, + "remote_vid": { + "type": "integer", + "maximum": 4094, + "minimum": 1, + "title": "Remote VLAN ID", + "description": "Numeric VLAN ID (1-4094)" + }, + "description": { + "type": "string", + "maxLength": 200 + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVMInterfaceRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "virtual_machine": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefVirtualMachineRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "enabled": { + "type": "boolean" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedVMInterfaceRequest" + } + ], + "nullable": true + }, + "bridge": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedVMInterfaceRequest" + } + ], + "nullable": true + }, + "mtu": { + "type": "integer", + "maximum": 65536, + "minimum": 1, + "nullable": true + }, + "primary_mac_address": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefMACAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "mode": { + "enum": [ + "access", + "tagged", + "tagged-all", + "q-in-q", + "" + ], + "type": "string", + "description": "* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)", + "x-spec-enum-id": "84129b71b974ebe5" + }, + "untagged_vlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tagged_vlans": { + "type": "array", + "items": { + "type": "integer" + } + }, + "qinq_svlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vlan_translation_policy": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vrf": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVRFRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVRFRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "rd": { + "type": "string", + "nullable": true, + "title": "Route distinguisher", + "description": "Unique route distinguisher (as defined in RFC 4364)", + "maxLength": 21 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "enforce_unique": { + "type": "boolean", + "title": "Enforce unique space", + "description": "Prevent duplicate prefixes/IP addresses within this VRF" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "import_targets": { + "type": "array", + "items": { + "type": "integer" + } + }, + "export_targets": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVirtualChassisRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "domain": { + "type": "string", + "maxLength": 30 + }, + "master": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedDeviceRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVirtualCircuitRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "cid": { + "type": "string", + "minLength": 1, + "title": "Circuit ID", + "description": "Unique circuit ID", + "maxLength": 100 + }, + "provider_network": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefProviderNetworkRequest" + } + ] + }, + "provider_account": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefProviderAccountRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "type": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefVirtualCircuitTypeRequest" + } + ] + }, + "status": { + "enum": [ + "planned", + "provisioning", + "active", + "offline", + "deprovisioning", + "decommissioned" + ], + "type": "string", + "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned", + "x-spec-enum-id": "0a239d878b6666a4" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVirtualCircuitTerminationRequest": { + "type": "object", + "description": "Adds support for custom fields and tags.", + "properties": { + "id": { + "type": "integer" + }, + "virtual_circuit": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefVirtualCircuitRequest" + } + ] + }, + "role": { + "enum": [ + "peer", + "hub", + "spoke" + ], + "type": "string", + "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke", + "x-spec-enum-id": "0b3bfadcebd86b58" + }, + "interface": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefInterfaceRequest" + } + ] + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVirtualCircuitTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from OrganizationalModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "color": { + "type": "string", + "pattern": "^[0-9a-f]{6}$", + "maxLength": 6 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVirtualDeviceContextRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ] + }, + "identifier": { + "type": "integer", + "maximum": 32767, + "minimum": 0, + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip4": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip6": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "planned", + "offline" + ], + "type": "string", + "description": "* `active` - Active\n* `planned` - Planned\n* `offline` - Offline", + "x-spec-enum-id": "0e2c0919d51b83cb" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVirtualDiskRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "virtual_machine": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefVirtualMachineRequest" + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "size": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVirtualMachineTypeRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "default_platform": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPlatformRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "default_vcpus": { + "type": "number", + "format": "double", + "maximum": 10000, + "minimum": 0.01, + "exclusiveMaximum": true, + "nullable": true + }, + "default_memory": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "title": "Default memory (MB)" + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkVirtualMachineWithConfigContextRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "virtual_machine_type": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVirtualMachineTypeRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "role": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceRoleRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "offline", + "active", + "planned", + "staged", + "failed", + "decommissioning", + "paused" + ], + "type": "string", + "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning\n* `paused` - Paused", + "x-spec-enum-id": "effecc3b94e0b74b" + }, + "start_on_boot": { + "enum": [ + "on", + "off", + "laststate" + ], + "type": "string", + "description": "* `on` - On\n* `off` - Off\n* `laststate` - Last State", + "x-spec-enum-id": "610e33fc2fde73d6" + }, + "site": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefSiteRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "cluster": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefClusterRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "device": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefDeviceRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "platform": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefPlatformRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip4": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "primary_ip6": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefIPAddressRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "vcpus": { + "type": "number", + "format": "double", + "maximum": 10000, + "minimum": 0.01, + "exclusiveMaximum": true, + "nullable": true + }, + "memory": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "disk": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "serial": { + "type": "string", + "title": "Serial number", + "maxLength": 50 + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "local_context_data": { + "nullable": true, + "description": "Local config context data takes precedence over source contexts in the final rendered config context" + }, + "config_template": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefConfigTemplateRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkWebhookRequest": { + "type": "object", + "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "payload_url": { + "type": "string", + "minLength": 1, + "title": "URL", + "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.", + "maxLength": 500 + }, + "http_method": { + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "type": "string", + "description": "* `GET` - GET\n* `POST` - POST\n* `PUT` - PUT\n* `PATCH` - PATCH\n* `DELETE` - DELETE", + "x-spec-enum-id": "867bf764d3b1eeaa" + }, + "http_content_type": { + "type": "string", + "minLength": 1, + "description": "The complete list of official content types is available here.", + "maxLength": 100 + }, + "additional_headers": { + "type": "string", + "description": "User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. Headers should be defined in the format Name: Value. Jinja2 template processing is supported with the same context as the request body (below)." + }, + "body_template": { + "type": "string", + "description": "Jinja2 template for a custom request body. If blank, a JSON object representing the change will be included. Available context data includes: event, model, timestamp, username, request_id, and data." + }, + "secret": { + "type": "string", + "description": "When provided, the request will include a X-Hook-Signature header containing a HMAC hex digest of the payload body using the secret as the key. The secret is not transmitted in the request.", + "maxLength": 255 + }, + "ssl_verification": { + "type": "boolean", + "description": "Enable SSL certificate verification. Disable with caution!" + }, + "ca_file_path": { + "type": "string", + "nullable": true, + "description": "The specific CA certificate file to use for SSL verification. Leave blank to use the system defaults.", + "maxLength": 4096 + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkWirelessLANGroupRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from NestedGroupModel.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[-a-zA-Z0-9_]+$" + }, + "parent": { + "allOf": [ + { + "$ref": "#/components/schemas/NestedWirelessLANGroupRequest" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkWirelessLANRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "ssid": { + "type": "string", + "minLength": 1, + "maxLength": 32 + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "group": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefWirelessLANGroupRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "status": { + "enum": [ + "active", + "reserved", + "disabled", + "deprecated", + "" + ], + "type": "string", + "description": "* `active` - Active\n* `reserved` - Reserved\n* `disabled` - Disabled\n* `deprecated` - Deprecated", + "x-spec-enum-id": "e5549d7370ce2e6c" + }, + "vlan": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefVLANRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "scope_type": { + "type": "string", + "nullable": true + }, + "scope_id": { + "type": "integer", + "nullable": true + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "auth_type": { + "enum": [ + "open", + "wep", + "wpa-personal", + "wpa-enterprise", + "" + ], + "type": "string", + "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise", + "x-spec-enum-id": "e917c12aac765910" + }, + "auth_cipher": { + "enum": [ + "auto", + "tkip", + "aes", + "" + ], + "type": "string", + "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES", + "x-spec-enum-id": "42f867e89988bb0c" + }, + "auth_psk": { + "type": "string", + "title": "Pre-shared key", + "maxLength": 64 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, + "PatchedBulkWirelessLinkRequest": { + "type": "object", + "description": "Base serializer class for models inheriting from PrimaryModel.", + "properties": { + "id": { + "type": "integer" + }, + "interface_a": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefInterfaceRequest" + } + ] + }, + "interface_b": { + "oneOf": [ + { + "type": "integer" + }, + { + "$ref": "#/components/schemas/BriefInterfaceRequest" + } + ] + }, + "ssid": { + "type": "string", + "maxLength": 32 + }, + "status": { + "enum": [ + "connected", + "planned", + "decommissioning" + ], + "type": "string", + "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning", + "x-spec-enum-id": "80d251a40f3a3144" + }, + "tenant": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefTenantRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "auth_type": { + "enum": [ + "open", + "wep", + "wpa-personal", + "wpa-enterprise", + "" + ], + "type": "string", + "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise", + "x-spec-enum-id": "e917c12aac765910" + }, + "auth_cipher": { + "enum": [ + "auto", + "tkip", + "aes", + "" + ], + "type": "string", + "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES", + "x-spec-enum-id": "42f867e89988bb0c" + }, + "auth_psk": { + "type": "string", + "title": "Pre-shared key", + "maxLength": 64 + }, + "distance": { + "type": "number", + "format": "double", + "maximum": 1000000, + "minimum": -1000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, + "nullable": true + }, + "distance_unit": { + "enum": [ + "km", + "m", + "mi", + "ft", + "", + null + ], + "type": "string", + "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet", + "x-spec-enum-id": "b1169a409430c02e", + "nullable": true + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "owner": { + "oneOf": [ + { + "type": "integer" + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/BriefOwnerRequest" + } + ], + "nullable": true + } + ], + "nullable": true + }, + "comments": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NestedTagRequest" + } + }, + "custom_fields": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "id" + ] + }, "PatchedCableBundleRequest": { "type": "object", "description": "Base serializer class for models inheriting from PrimaryModel.", @@ -264808,6 +298057,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -264842,8 +298095,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -264972,6 +298225,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -265006,8 +298263,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -269221,6 +302478,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -269255,8 +302516,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -269385,6 +302646,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -269419,8 +302684,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -277132,6 +310397,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -277166,8 +310435,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "label": { "type": "string", @@ -277198,6 +310467,10 @@ "LC/PC", "LC/UPC", "LC/APC", + "MU", + "MU/PC", + "MU/UPC", + "MU/APC", "LSH", "LSH/PC", "LSH/UPC", @@ -277453,6 +310726,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -277487,8 +310764,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -277621,6 +310898,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -277655,8 +310936,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "label": { "type": "string", @@ -277687,6 +310968,10 @@ "LC/PC", "LC/UPC", "LC/APC", + "MU", + "MU/PC", + "MU/UPC", + "MU/APC", "LSH", "LSH/PC", "LSH/UPC", @@ -277883,6 +311168,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -277917,8 +311206,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -287946,6 +321235,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -287980,8 +321273,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -288115,6 +321408,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -288149,8 +321446,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -292474,6 +325771,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -292508,8 +325809,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", @@ -292643,6 +325944,10 @@ "lc-pc", "lc-upc", "lc-apc", + "mu", + "mu-pc", + "mu-upc", + "mu-apc", "lsh", "lsh-pc", "lsh-upc", @@ -292677,8 +325982,8 @@ "other" ], "type": "string", - "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", - "x-spec-enum-id": "2696b7065f33307c" + "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other", + "x-spec-enum-id": "b64d6804afec405c" }, "color": { "type": "string", diff --git a/docs/release-notes/version-4.6.md b/docs/release-notes/version-4.6.md index f4ea93052..0e152e5cb 100644 --- a/docs/release-notes/version-4.6.md +++ b/docs/release-notes/version-4.6.md @@ -1,5 +1,51 @@ # NetBox v4.6 +## v4.6.3 (2026-06-16) + +### Enhancements + +* [#17598](https://github.com/netbox-community/netbox/issues/17598) - Add bulk creation support for VLANs +* [#21666](https://github.com/netbox-community/netbox/issues/21666) - Add MU connector type for fiber ports and cables +* [#22361](https://github.com/netbox-community/netbox/issues/22361) - Introduce an `ArrayAttr` UI panel attribute for rendering array field values +* [#22457](https://github.com/netbox-community/netbox/issues/22457) - Use `hmac.compare_digest()` for constant-time authentication of API tokens + +### Performance Improvements + +* [#21870](https://github.com/netbox-community/netbox/issues/21870) - Optimize prefix availability calculations +* [#22375](https://github.com/netbox-community/netbox/issues/22375) - Improve efficiency of filtering VLANs by interface + +### Bug Fixes + +* [#21338](https://github.com/netbox-community/netbox/issues/21338) - Include connected endpoint data in interface webhooks generated during cable creation +* [#21895](https://github.com/netbox-community/netbox/issues/21895) - Restore pagination controls for job log entries (previously limited to 50 rows) +* [#22210](https://github.com/netbox-community/netbox/issues/22210) - Respect saved filters when rendering IPAM child availability views in additional tabs +* [#22237](https://github.com/netbox-community/netbox/issues/22237) - Fix server error when opening the standalone "Add Table Configuration" page +* [#22245](https://github.com/netbox-community/netbox/issues/22245) - Include the `id` field in the OpenAPI request schemas for bulk PATCH/PUT endpoints +* [#22251](https://github.com/netbox-community/netbox/issues/22251) - Re-parent child module bays when a multi-bay module is moved to a new bay +* [#22273](https://github.com/netbox-community/netbox/issues/22273) - Fix migration failure when a service has several thousand ports defined +* [#22303](https://github.com/netbox-community/netbox/issues/22303) - Add the missing `fields` parameter to the OpenAPI schema +* [#22324](https://github.com/netbox-community/netbox/issues/22324) - Fix GraphQL filtering of custom field choice set extra choices +* [#22340](https://github.com/netbox-community/netbox/issues/22340) - Display a token's allowed IPs as comma-separated strings rather than `IPNetwork` objects +* [#22346](https://github.com/netbox-community/netbox/issues/22346) - Render SSO/SAML authentication failures as a login page message instead of an HTTP 500 error +* [#22357](https://github.com/netbox-community/netbox/issues/22357) - Remove the unused `local_context_data` field from `dcim.Module` (which no longer inherits from `ConfigContextModel`) +* [#22376](https://github.com/netbox-community/netbox/issues/22376) - Fix `AssertionError` in event rule script jobs when a device type has an image attached +* [#22388](https://github.com/netbox-community/netbox/issues/22388) - Pin redis-py to <8.0 to avoid a startup failure on older Redis releases +* [#22397](https://github.com/netbox-community/netbox/issues/22397) - Fix `AttributeError` exception when an unauthenticated user attempts to export devices +* [#22399](https://github.com/netbox-community/netbox/issues/22399) - Enforce object permissions on the related object when serving static media +* [#22427](https://github.com/netbox-community/netbox/issues/22427) - Validate `JSONFilter.path` to prevent ORM operator injection over JSONField contents in the GraphQL API +* [#22429](https://github.com/netbox-community/netbox/issues/22429) - Enforce `ObjectPermission` constraints on `grant_token` in the REST API +* [#22431](https://github.com/netbox-community/netbox/issues/22431) - Use a cryptographically secure random number generator when generating API tokens +* [#22444](https://github.com/netbox-community/netbox/issues/22444) - Fix `KeyError` exception on the power feed detail view when the locale is not English +* [#22448](https://github.com/netbox-community/netbox/issues/22448) - Ensure all object representations are escaped under `handle_protectederror()` +* [#22454](https://github.com/netbox-community/netbox/issues/22454) - Fix serialization of decimal custom field values to avoid spurious changelog entries +* [#22466](https://github.com/netbox-community/netbox/issues/22466) - Fix test failure against SSL-enabled PosgtreSQL + +### Deprecations + +* [#22392](https://github.com/netbox-community/netbox/issues/22392) - Deprecate support for Redis 5.x (to be removed in v4.7) + +--- + ## v4.6.2 (2026-06-02) ### Enhancements diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 001c84bb1..05b3de92d 100644 --- a/netbox/project-static/dist/netbox.js +++ b/netbox/project-static/dist/netbox.js @@ -1,4 +1,4 @@ -"use strict";(()=>{var iu=Object.create;var Di=Object.defineProperty,ru=Object.defineProperties,ou=Object.getOwnPropertyDescriptor,su=Object.getOwnPropertyDescriptors,au=Object.getOwnPropertyNames,ps=Object.getOwnPropertySymbols,lu=Object.getPrototypeOf,ms=Object.prototype.hasOwnProperty,cu=Object.prototype.propertyIsEnumerable;var Wr=(n,e,t)=>e in n?Di(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,D=(n,e)=>{for(var t in e||(e={}))ms.call(e,t)&&Wr(n,t,e[t]);if(ps)for(var t of ps(e))cu.call(e,t)&&Wr(n,t,e[t]);return n},se=(n,e)=>ru(n,su(e));var uu=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),gs=(n,e)=>{for(var t in e)Di(n,t,{get:e[t],enumerable:!0})},du=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of au(e))!ms.call(n,r)&&r!==t&&Di(n,r,{get:()=>e[r],enumerable:!(i=ou(e,r))||i.enumerable});return n};var fu=(n,e,t)=>(t=n!=null?iu(lu(n)):{},du(e||!n||!n.__esModule?Di(t,"default",{value:n,enumerable:!0}):t,n));var le=(n,e,t)=>Wr(n,typeof e!="symbol"?e+"":e,t);var at=(n,e,t)=>new Promise((i,r)=>{var o=l=>{try{a(t.next(l))}catch(c){r(c)}},s=l=>{try{a(t.throw(l))}catch(c){r(c)}},a=l=>l.done?i(l.value):Promise.resolve(l.value).then(o,s);a((t=t.apply(n,e)).next())});var bc=uu((pi,ns)=>{(function(e,t){typeof pi=="object"&&typeof ns=="object"?ns.exports=t():typeof define=="function"&&define.amd?define([],t):typeof pi=="object"?pi.ClipboardJS=t():e.ClipboardJS=t()})(pi,function(){return(function(){var n={686:(function(i,r,o){"use strict";o.d(r,{default:function(){return Re}});var s=o(279),a=o.n(s),l=o(370),c=o.n(l),u=o(817),d=o.n(u);function p(q){try{return document.execCommand(q)}catch(M){return!1}}var y=function(M){var A=d()(M);return p("cut"),A},m=y;function g(q){var M=document.documentElement.getAttribute("dir")==="rtl",A=document.createElement("textarea");A.style.fontSize="12pt",A.style.border="0",A.style.padding="0",A.style.margin="0",A.style.position="absolute",A.style[M?"right":"left"]="-9999px";var B=window.pageYOffset||document.documentElement.scrollTop;return A.style.top="".concat(B,"px"),A.setAttribute("readonly",""),A.value=q,A}var _=function(M,A){var B=g(M);A.container.appendChild(B);var V=d()(B);return p("copy"),B.remove(),V},C=function(M){var A=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},B="";return typeof M=="string"?B=_(M,A):M instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(M==null?void 0:M.type)?B=_(M.value,A):(B=d()(M),p("copy")),B},O=C;function w(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?w=function(A){return typeof A}:w=function(A){return A&&typeof Symbol=="function"&&A.constructor===Symbol&&A!==Symbol.prototype?"symbol":typeof A},w(q)}var T=function(){var M=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},A=M.action,B=A===void 0?"copy":A,V=M.container,U=M.target,Y=M.text;if(B!=="copy"&&B!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(U!==void 0)if(U&&w(U)==="object"&&U.nodeType===1){if(B==="copy"&&U.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(B==="cut"&&(U.hasAttribute("readonly")||U.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Y)return O(Y,{container:V});if(U)return B==="cut"?m(U):O(U,{container:V})},$=T;function j(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?j=function(A){return typeof A}:j=function(A){return A&&typeof Symbol=="function"&&A.constructor===Symbol&&A!==Symbol.prototype?"symbol":typeof A},j(q)}function H(q,M){if(!(q instanceof M))throw new TypeError("Cannot call a class as a function")}function I(q,M){for(var A=0;A0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof V.action=="function"?V.action:this.defaultAction,this.target=typeof V.target=="function"?V.target:this.defaultTarget,this.text=typeof V.text=="function"?V.text:this.defaultText,this.container=j(V.container)==="object"?V.container:document.body}},{key:"listenClick",value:function(V){var U=this;this.listener=c()(V,"click",function(Y){return U.onClick(Y)})}},{key:"onClick",value:function(V){var U=V.delegateTarget||V.currentTarget,Y=this.action(U)||"copy",ee=$({action:Y,container:this.container,target:this.target(U),text:this.text(U)});this.emit(ee?"success":"error",{action:Y,text:ee,trigger:U,clearSelection:function(){U&&U.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(V){return ne("action",V)}},{key:"defaultTarget",value:function(V){var U=ne("target",V);if(U)return document.querySelector(U)}},{key:"defaultText",value:function(V){return ne("text",V)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(V){var U=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return O(V,U)}},{key:"cut",value:function(V){return m(V)}},{key:"isSupported",value:function(){var V=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],U=typeof V=="string"?[V]:V,Y=!!document.queryCommandSupported;return U.forEach(function(ee){Y=Y&&!!document.queryCommandSupported(ee)}),Y}}]),A})(a()),Re=Ue}),828:(function(i){var r=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var o=Element.prototype;o.matches=o.matchesSelector||o.mozMatchesSelector||o.msMatchesSelector||o.oMatchesSelector||o.webkitMatchesSelector}function s(a,l){for(;a&&a.nodeType!==r;){if(typeof a.matches=="function"&&a.matches(l))return a;a=a.parentNode}}i.exports=s}),438:(function(i,r,o){var s=o(828);function a(u,d,p,y,m){var g=c.apply(this,arguments);return u.addEventListener(p,g,m),{destroy:function(){u.removeEventListener(p,g,m)}}}function l(u,d,p,y,m){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof p=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(g){return a(g,d,p,y,m)}))}function c(u,d,p,y){return function(m){m.delegateTarget=s(m.target,d),m.delegateTarget&&y.call(u,m)}}i.exports=l}),879:(function(i,r){r.node=function(o){return o!==void 0&&o instanceof HTMLElement&&o.nodeType===1},r.nodeList=function(o){var s=Object.prototype.toString.call(o);return o!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in o&&(o.length===0||r.node(o[0]))},r.string=function(o){return typeof o=="string"||o instanceof String},r.fn=function(o){var s=Object.prototype.toString.call(o);return s==="[object Function]"}}),370:(function(i,r,o){var s=o(879),a=o(438);function l(p,y,m){if(!p&&!y&&!m)throw new Error("Missing required arguments");if(!s.string(y))throw new TypeError("Second argument must be a String");if(!s.fn(m))throw new TypeError("Third argument must be a Function");if(s.node(p))return c(p,y,m);if(s.nodeList(p))return u(p,y,m);if(s.string(p))return d(p,y,m);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(p,y,m){return p.addEventListener(y,m),{destroy:function(){p.removeEventListener(y,m)}}}function u(p,y,m){return Array.prototype.forEach.call(p,function(g){g.addEventListener(y,m)}),{destroy:function(){Array.prototype.forEach.call(p,function(g){g.removeEventListener(y,m)})}}}function d(p,y,m){return a(document.body,p,y,m)}i.exports=l}),817:(function(i){function r(o){var s;if(o.nodeName==="SELECT")o.focus(),s=o.value;else if(o.nodeName==="INPUT"||o.nodeName==="TEXTAREA"){var a=o.hasAttribute("readonly");a||o.setAttribute("readonly",""),o.select(),o.setSelectionRange(0,o.value.length),a||o.removeAttribute("readonly"),s=o.value}else{o.hasAttribute("contenteditable")&&o.focus();var l=window.getSelection(),c=document.createRange();c.selectNodeContents(o),l.removeAllRanges(),l.addRange(c),s=l.toString()}return s}i.exports=r}),279:(function(i){function r(){}r.prototype={on:function(o,s,a){var l=this.e||(this.e={});return(l[o]||(l[o]=[])).push({fn:s,ctx:a}),this},once:function(o,s,a){var l=this;function c(){l.off(o,c),s.apply(a,arguments)}return c._=s,this.on(o,c,a)},emit:function(o){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[o]||[]).slice(),l=0,c=a.length;for(l;lws,afterRead:()=>Es,afterWrite:()=>Cs,applyStyles:()=>fn,arrow:()=>Li,auto:()=>Bn,basePlacements:()=>lt,beforeMain:()=>bs,beforeRead:()=>vs,beforeWrite:()=>xs,bottom:()=>ge,clippingParents:()=>qr,computeStyles:()=>pn,createPopper:()=>Kn,createPopperBase:()=>Hs,createPopperLite:()=>Is,detectOverflow:()=>ke,end:()=>bt,eventListeners:()=>mn,flip:()=>Hi,hide:()=>Ii,left:()=>pe,main:()=>_s,modifierPhases:()=>Yr,offset:()=>Ri,placements:()=>zn,popper:()=>$t,popperGenerator:()=>Yt,popperOffsets:()=>yn,preventOverflow:()=>Pi,read:()=>ys,reference:()=>Ur,right:()=>me,start:()=>rt,top:()=>de,variationPlacements:()=>Oi,viewport:()=>Vn,write:()=>Ts});var de="top",ge="bottom",me="right",pe="left",Bn="auto",lt=[de,ge,me,pe],rt="start",bt="end",qr="clippingParents",Vn="viewport",$t="popper",Ur="reference",Oi=lt.reduce(function(n,e){return n.concat([e+"-"+rt,e+"-"+bt])},[]),zn=[].concat(lt,[Bn]).reduce(function(n,e){return n.concat([e,e+"-"+rt,e+"-"+bt])},[]),vs="beforeRead",ys="read",Es="afterRead",bs="beforeMain",_s="main",ws="afterMain",xs="beforeWrite",Ts="write",Cs="afterWrite",Yr=[vs,ys,Es,bs,_s,ws,xs,Ts,Cs];function xe(n){return n?(n.nodeName||"").toLowerCase():null}function ce(n){if(n==null)return window;if(n.toString()!=="[object Window]"){var e=n.ownerDocument;return e&&e.defaultView||window}return n}function Ye(n){var e=ce(n).Element;return n instanceof e||n instanceof Element}function _e(n){var e=ce(n).HTMLElement;return n instanceof e||n instanceof HTMLElement}function dn(n){if(typeof ShadowRoot=="undefined")return!1;var e=ce(n).ShadowRoot;return n instanceof e||n instanceof ShadowRoot}function hu(n){var e=n.state;Object.keys(e.elements).forEach(function(t){var i=e.styles[t]||{},r=e.attributes[t]||{},o=e.elements[t];!_e(o)||!xe(o)||(Object.assign(o.style,i),Object.keys(r).forEach(function(s){var a=r[s];a===!1?o.removeAttribute(s):o.setAttribute(s,a===!0?"":a)}))})}function pu(n){var e=n.state,t={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,t.popper),e.styles=t,e.elements.arrow&&Object.assign(e.elements.arrow.style,t.arrow),function(){Object.keys(e.elements).forEach(function(i){var r=e.elements[i],o=e.attributes[i]||{},s=Object.keys(e.styles.hasOwnProperty(i)?e.styles[i]:t[i]),a=s.reduce(function(l,c){return l[c]="",l},{});!_e(r)||!xe(r)||(Object.assign(r.style,a),Object.keys(o).forEach(function(l){r.removeAttribute(l)}))})}}var fn={name:"applyStyles",enabled:!0,phase:"write",fn:hu,effect:pu,requires:["computeStyles"]};function Te(n){return n.split("-")[0]}var Ze=Math.max,Bt=Math.min,ct=Math.round;function hn(){var n=navigator.userAgentData;return n!=null&&n.brands&&Array.isArray(n.brands)?n.brands.map(function(e){return e.brand+"/"+e.version}).join(" "):navigator.userAgent}function jn(){return!/^((?!chrome|android).)*safari/i.test(hn())}function Ge(n,e,t){e===void 0&&(e=!1),t===void 0&&(t=!1);var i=n.getBoundingClientRect(),r=1,o=1;e&&_e(n)&&(r=n.offsetWidth>0&&ct(i.width)/n.offsetWidth||1,o=n.offsetHeight>0&&ct(i.height)/n.offsetHeight||1);var s=Ye(n)?ce(n):window,a=s.visualViewport,l=!jn()&&t,c=(i.left+(l&&a?a.offsetLeft:0))/r,u=(i.top+(l&&a?a.offsetTop:0))/o,d=i.width/r,p=i.height/o;return{width:d,height:p,top:u,right:c+d,bottom:u+p,left:c,x:c,y:u}}function Vt(n){var e=Ge(n),t=n.offsetWidth,i=n.offsetHeight;return Math.abs(e.width-t)<=1&&(t=e.width),Math.abs(e.height-i)<=1&&(i=e.height),{x:n.offsetLeft,y:n.offsetTop,width:t,height:i}}function Wn(n,e){var t=e.getRootNode&&e.getRootNode();if(n.contains(e))return!0;if(t&&dn(t)){var i=e;do{if(i&&n.isSameNode(i))return!0;i=i.parentNode||i.host}while(i)}return!1}function Ne(n){return ce(n).getComputedStyle(n)}function Gr(n){return["table","td","th"].indexOf(xe(n))>=0}function Se(n){return((Ye(n)?n.ownerDocument:n.document)||window.document).documentElement}function ut(n){return xe(n)==="html"?n:n.assignedSlot||n.parentNode||(dn(n)?n.host:null)||Se(n)}function Ss(n){return!_e(n)||Ne(n).position==="fixed"?null:n.offsetParent}function mu(n){var e=/firefox/i.test(hn()),t=/Trident/i.test(hn());if(t&&_e(n)){var i=Ne(n);if(i.position==="fixed")return null}var r=ut(n);for(dn(r)&&(r=r.host);_e(r)&&["html","body"].indexOf(xe(r))<0;){var o=Ne(r);if(o.transform!=="none"||o.perspective!=="none"||o.contain==="paint"||["transform","perspective"].indexOf(o.willChange)!==-1||e&&o.willChange==="filter"||e&&o.filter&&o.filter!=="none")return r;r=r.parentNode}return null}function et(n){for(var e=ce(n),t=Ss(n);t&&Gr(t)&&Ne(t).position==="static";)t=Ss(t);return t&&(xe(t)==="html"||xe(t)==="body"&&Ne(t).position==="static")?e:t||mu(n)||e}function zt(n){return["top","bottom"].indexOf(n)>=0?"x":"y"}function jt(n,e,t){return Ze(n,Bt(e,t))}function As(n,e,t){var i=jt(n,e,t);return i>t?t:i}function qn(){return{top:0,right:0,bottom:0,left:0}}function Un(n){return Object.assign({},qn(),n)}function Yn(n,e){return e.reduce(function(t,i){return t[i]=n,t},{})}var gu=function(e,t){return e=typeof e=="function"?e(Object.assign({},t.rects,{placement:t.placement})):e,Un(typeof e!="number"?e:Yn(e,lt))};function vu(n){var e,t=n.state,i=n.name,r=n.options,o=t.elements.arrow,s=t.modifiersData.popperOffsets,a=Te(t.placement),l=zt(a),c=[pe,me].indexOf(a)>=0,u=c?"height":"width";if(!(!o||!s)){var d=gu(r.padding,t),p=Vt(o),y=l==="y"?de:pe,m=l==="y"?ge:me,g=t.rects.reference[u]+t.rects.reference[l]-s[l]-t.rects.popper[u],_=s[l]-t.rects.reference[l],C=et(o),O=C?l==="y"?C.clientHeight||0:C.clientWidth||0:0,w=g/2-_/2,T=d[y],$=O-p[u]-d[m],j=O/2-p[u]/2+w,H=jt(T,j,$),I=l;t.modifiersData[i]=(e={},e[I]=H,e.centerOffset=H-j,e)}}function yu(n){var e=n.state,t=n.options,i=t.element,r=i===void 0?"[data-popper-arrow]":i;r!=null&&(typeof r=="string"&&(r=e.elements.popper.querySelector(r),!r)||Wn(e.elements.popper,r)&&(e.elements.arrow=r))}var Li={name:"arrow",enabled:!0,phase:"main",fn:vu,effect:yu,requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Ke(n){return n.split("-")[1]}var Eu={top:"auto",right:"auto",bottom:"auto",left:"auto"};function bu(n,e){var t=n.x,i=n.y,r=e.devicePixelRatio||1;return{x:ct(t*r)/r||0,y:ct(i*r)/r||0}}function Ds(n){var e,t=n.popper,i=n.popperRect,r=n.placement,o=n.variation,s=n.offsets,a=n.position,l=n.gpuAcceleration,c=n.adaptive,u=n.roundOffsets,d=n.isFixed,p=s.x,y=p===void 0?0:p,m=s.y,g=m===void 0?0:m,_=typeof u=="function"?u({x:y,y:g}):{x:y,y:g};y=_.x,g=_.y;var C=s.hasOwnProperty("x"),O=s.hasOwnProperty("y"),w=pe,T=de,$=window;if(c){var j=et(t),H="clientHeight",I="clientWidth";if(j===ce(t)&&(j=Se(t),Ne(j).position!=="static"&&a==="absolute"&&(H="scrollHeight",I="scrollWidth")),j=j,r===de||(r===pe||r===me)&&o===bt){T=ge;var L=d&&j===$&&$.visualViewport?$.visualViewport.height:j[H];g-=L-i.height,g*=l?1:-1}if(r===pe||(r===de||r===ge)&&o===bt){w=me;var W=d&&j===$&&$.visualViewport?$.visualViewport.width:j[I];y-=W-i.width,y*=l?1:-1}}var G=Object.assign({position:a},c&&Eu),Q=u===!0?bu({x:y,y:g},ce(t)):{x:y,y:g};if(y=Q.x,g=Q.y,l){var Z;return Object.assign({},G,(Z={},Z[T]=O?"0":"",Z[w]=C?"0":"",Z.transform=($.devicePixelRatio||1)<=1?"translate("+y+"px, "+g+"px)":"translate3d("+y+"px, "+g+"px, 0)",Z))}return Object.assign({},G,(e={},e[T]=O?g+"px":"",e[w]=C?y+"px":"",e.transform="",e))}function _u(n){var e=n.state,t=n.options,i=t.gpuAcceleration,r=i===void 0?!0:i,o=t.adaptive,s=o===void 0?!0:o,a=t.roundOffsets,l=a===void 0?!0:a,c={placement:Te(e.placement),variation:Ke(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:r,isFixed:e.options.strategy==="fixed"};e.modifiersData.popperOffsets!=null&&(e.styles.popper=Object.assign({},e.styles.popper,Ds(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:s,roundOffsets:l})))),e.modifiersData.arrow!=null&&(e.styles.arrow=Object.assign({},e.styles.arrow,Ds(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})}var pn={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:_u,data:{}};var Mi={passive:!0};function wu(n){var e=n.state,t=n.instance,i=n.options,r=i.scroll,o=r===void 0?!0:r,s=i.resize,a=s===void 0?!0:s,l=ce(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach(function(u){u.addEventListener("scroll",t.update,Mi)}),a&&l.addEventListener("resize",t.update,Mi),function(){o&&c.forEach(function(u){u.removeEventListener("scroll",t.update,Mi)}),a&&l.removeEventListener("resize",t.update,Mi)}}var mn={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:wu,data:{}};var xu={left:"right",right:"left",bottom:"top",top:"bottom"};function gn(n){return n.replace(/left|right|bottom|top/g,function(e){return xu[e]})}var Tu={start:"end",end:"start"};function Ni(n){return n.replace(/start|end/g,function(e){return Tu[e]})}function Wt(n){var e=ce(n),t=e.pageXOffset,i=e.pageYOffset;return{scrollLeft:t,scrollTop:i}}function qt(n){return Ge(Se(n)).left+Wt(n).scrollLeft}function Kr(n,e){var t=ce(n),i=Se(n),r=t.visualViewport,o=i.clientWidth,s=i.clientHeight,a=0,l=0;if(r){o=r.width,s=r.height;var c=jn();(c||!c&&e==="fixed")&&(a=r.offsetLeft,l=r.offsetTop)}return{width:o,height:s,x:a+qt(n),y:l}}function Xr(n){var e,t=Se(n),i=Wt(n),r=(e=n.ownerDocument)==null?void 0:e.body,o=Ze(t.scrollWidth,t.clientWidth,r?r.scrollWidth:0,r?r.clientWidth:0),s=Ze(t.scrollHeight,t.clientHeight,r?r.scrollHeight:0,r?r.clientHeight:0),a=-i.scrollLeft+qt(n),l=-i.scrollTop;return Ne(r||t).direction==="rtl"&&(a+=Ze(t.clientWidth,r?r.clientWidth:0)-o),{width:o,height:s,x:a,y:l}}function Ut(n){var e=Ne(n),t=e.overflow,i=e.overflowX,r=e.overflowY;return/auto|scroll|overlay|hidden/.test(t+r+i)}function ki(n){return["html","body","#document"].indexOf(xe(n))>=0?n.ownerDocument.body:_e(n)&&Ut(n)?n:ki(ut(n))}function _t(n,e){var t;e===void 0&&(e=[]);var i=ki(n),r=i===((t=n.ownerDocument)==null?void 0:t.body),o=ce(i),s=r?[o].concat(o.visualViewport||[],Ut(i)?i:[]):i,a=e.concat(s);return r?a:a.concat(_t(ut(s)))}function vn(n){return Object.assign({},n,{left:n.x,top:n.y,right:n.x+n.width,bottom:n.y+n.height})}function Cu(n,e){var t=Ge(n,!1,e==="fixed");return t.top=t.top+n.clientTop,t.left=t.left+n.clientLeft,t.bottom=t.top+n.clientHeight,t.right=t.left+n.clientWidth,t.width=n.clientWidth,t.height=n.clientHeight,t.x=t.left,t.y=t.top,t}function Os(n,e,t){return e===Vn?vn(Kr(n,t)):Ye(e)?Cu(e,t):vn(Xr(Se(n)))}function Su(n){var e=_t(ut(n)),t=["absolute","fixed"].indexOf(Ne(n).position)>=0,i=t&&_e(n)?et(n):n;return Ye(i)?e.filter(function(r){return Ye(r)&&Wn(r,i)&&xe(r)!=="body"}):[]}function Qr(n,e,t,i){var r=e==="clippingParents"?Su(n):[].concat(e),o=[].concat(r,[t]),s=o[0],a=o.reduce(function(l,c){var u=Os(n,c,i);return l.top=Ze(u.top,l.top),l.right=Bt(u.right,l.right),l.bottom=Bt(u.bottom,l.bottom),l.left=Ze(u.left,l.left),l},Os(n,s,i));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function Gn(n){var e=n.reference,t=n.element,i=n.placement,r=i?Te(i):null,o=i?Ke(i):null,s=e.x+e.width/2-t.width/2,a=e.y+e.height/2-t.height/2,l;switch(r){case de:l={x:s,y:e.y-t.height};break;case ge:l={x:s,y:e.y+e.height};break;case me:l={x:e.x+e.width,y:a};break;case pe:l={x:e.x-t.width,y:a};break;default:l={x:e.x,y:e.y}}var c=r?zt(r):null;if(c!=null){var u=c==="y"?"height":"width";switch(o){case rt:l[c]=l[c]-(e[u]/2-t[u]/2);break;case bt:l[c]=l[c]+(e[u]/2-t[u]/2);break;default:}}return l}function ke(n,e){e===void 0&&(e={});var t=e,i=t.placement,r=i===void 0?n.placement:i,o=t.strategy,s=o===void 0?n.strategy:o,a=t.boundary,l=a===void 0?qr:a,c=t.rootBoundary,u=c===void 0?Vn:c,d=t.elementContext,p=d===void 0?$t:d,y=t.altBoundary,m=y===void 0?!1:y,g=t.padding,_=g===void 0?0:g,C=Un(typeof _!="number"?_:Yn(_,lt)),O=p===$t?Ur:$t,w=n.rects.popper,T=n.elements[m?O:p],$=Qr(Ye(T)?T:T.contextElement||Se(n.elements.popper),l,u,s),j=Ge(n.elements.reference),H=Gn({reference:j,element:w,strategy:"absolute",placement:r}),I=vn(Object.assign({},w,H)),L=p===$t?I:j,W={top:$.top-L.top+C.top,bottom:L.bottom-$.bottom+C.bottom,left:$.left-L.left+C.left,right:L.right-$.right+C.right},G=n.modifiersData.offset;if(p===$t&&G){var Q=G[r];Object.keys(W).forEach(function(Z){var he=[me,ge].indexOf(Z)>=0?1:-1,Ce=[de,ge].indexOf(Z)>=0?"y":"x";W[Z]+=Q[Ce]*he})}return W}function Jr(n,e){e===void 0&&(e={});var t=e,i=t.placement,r=t.boundary,o=t.rootBoundary,s=t.padding,a=t.flipVariations,l=t.allowedAutoPlacements,c=l===void 0?zn:l,u=Ke(i),d=u?a?Oi:Oi.filter(function(m){return Ke(m)===u}):lt,p=d.filter(function(m){return c.indexOf(m)>=0});p.length===0&&(p=d);var y=p.reduce(function(m,g){return m[g]=ke(n,{placement:g,boundary:r,rootBoundary:o,padding:s})[Te(g)],m},{});return Object.keys(y).sort(function(m,g){return y[m]-y[g]})}function Au(n){if(Te(n)===Bn)return[];var e=gn(n);return[Ni(n),e,Ni(e)]}function Du(n){var e=n.state,t=n.options,i=n.name;if(!e.modifiersData[i]._skip){for(var r=t.mainAxis,o=r===void 0?!0:r,s=t.altAxis,a=s===void 0?!0:s,l=t.fallbackPlacements,c=t.padding,u=t.boundary,d=t.rootBoundary,p=t.altBoundary,y=t.flipVariations,m=y===void 0?!0:y,g=t.allowedAutoPlacements,_=e.options.placement,C=Te(_),O=C===_,w=l||(O||!m?[gn(_)]:Au(_)),T=[_].concat(w).reduce(function(V,U){return V.concat(Te(U)===Bn?Jr(e,{placement:U,boundary:u,rootBoundary:d,padding:c,flipVariations:m,allowedAutoPlacements:g}):U)},[]),$=e.rects.reference,j=e.rects.popper,H=new Map,I=!0,L=T[0],W=0;W=0,Ce=he?"width":"height",ie=ke(e,{placement:G,boundary:u,rootBoundary:d,altBoundary:p,padding:c}),ne=he?Z?me:pe:Z?ge:de;$[Ce]>j[Ce]&&(ne=gn(ne));var Ue=gn(ne),Re=[];if(o&&Re.push(ie[Q]<=0),a&&Re.push(ie[ne]<=0,ie[Ue]<=0),Re.every(function(V){return V})){L=G,I=!1;break}H.set(G,Re)}if(I)for(var q=m?3:1,M=function(U){var Y=T.find(function(ee){var oe=H.get(ee);if(oe)return oe.slice(0,U).every(function(Et){return Et})});if(Y)return L=Y,"break"},A=q;A>0;A--){var B=M(A);if(B==="break")break}e.placement!==L&&(e.modifiersData[i]._skip=!0,e.placement=L,e.reset=!0)}}var Hi={name:"flip",enabled:!0,phase:"main",fn:Du,requiresIfExists:["offset"],data:{_skip:!1}};function Ls(n,e,t){return t===void 0&&(t={x:0,y:0}),{top:n.top-e.height-t.y,right:n.right-e.width+t.x,bottom:n.bottom-e.height+t.y,left:n.left-e.width-t.x}}function Ms(n){return[de,me,ge,pe].some(function(e){return n[e]>=0})}function Ou(n){var e=n.state,t=n.name,i=e.rects.reference,r=e.rects.popper,o=e.modifiersData.preventOverflow,s=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=Ls(s,i),c=Ls(a,r,o),u=Ms(l),d=Ms(c);e.modifiersData[t]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:u,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":u,"data-popper-escaped":d})}var Ii={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:Ou};function Lu(n,e,t){var i=Te(n),r=[pe,de].indexOf(i)>=0?-1:1,o=typeof t=="function"?t(Object.assign({},e,{placement:n})):t,s=o[0],a=o[1];return s=s||0,a=(a||0)*r,[pe,me].indexOf(i)>=0?{x:a,y:s}:{x:s,y:a}}function Mu(n){var e=n.state,t=n.options,i=n.name,r=t.offset,o=r===void 0?[0,0]:r,s=zn.reduce(function(u,d){return u[d]=Lu(d,e.rects,o),u},{}),a=s[e.placement],l=a.x,c=a.y;e.modifiersData.popperOffsets!=null&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[i]=s}var Ri={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:Mu};function Nu(n){var e=n.state,t=n.name;e.modifiersData[t]=Gn({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})}var yn={name:"popperOffsets",enabled:!0,phase:"read",fn:Nu,data:{}};function Zr(n){return n==="x"?"y":"x"}function ku(n){var e=n.state,t=n.options,i=n.name,r=t.mainAxis,o=r===void 0?!0:r,s=t.altAxis,a=s===void 0?!1:s,l=t.boundary,c=t.rootBoundary,u=t.altBoundary,d=t.padding,p=t.tether,y=p===void 0?!0:p,m=t.tetherOffset,g=m===void 0?0:m,_=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:u}),C=Te(e.placement),O=Ke(e.placement),w=!O,T=zt(C),$=Zr(T),j=e.modifiersData.popperOffsets,H=e.rects.reference,I=e.rects.popper,L=typeof g=="function"?g(Object.assign({},e.rects,{placement:e.placement})):g,W=typeof L=="number"?{mainAxis:L,altAxis:L}:Object.assign({mainAxis:0,altAxis:0},L),G=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,Q={x:0,y:0};if(j){if(o){var Z,he=T==="y"?de:pe,Ce=T==="y"?ge:me,ie=T==="y"?"height":"width",ne=j[T],Ue=ne+_[he],Re=ne-_[Ce],q=y?-I[ie]/2:0,M=O===rt?H[ie]:I[ie],A=O===rt?-I[ie]:-H[ie],B=e.elements.arrow,V=y&&B?Vt(B):{width:0,height:0},U=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:qn(),Y=U[he],ee=U[Ce],oe=jt(0,H[ie],V[ie]),Et=w?H[ie]/2-q-oe-Y-W.mainAxis:M-oe-Y-W.mainAxis,Fr=w?-H[ie]/2+q+oe+ee+W.mainAxis:A+oe+ee+W.mainAxis,rn=e.elements.arrow&&et(e.elements.arrow),on=rn?T==="y"?rn.clientTop||0:rn.clientLeft||0:0,_i=(Z=G==null?void 0:G[T])!=null?Z:0,$r=ne+Et-_i-on,wi=ne+Fr-_i,xi=jt(y?Bt(Ue,$r):Ue,ne,y?Ze(Re,wi):Re);j[T]=xi,Q[T]=xi-ne}if(a){var Rn,Ti=T==="x"?de:pe,sn=T==="x"?ge:me,ot=j[$],an=$==="y"?"height":"width",Pn=ot+_[Ti],ln=ot-_[sn],cn=[de,pe].indexOf(C)!==-1,Ft=(Rn=G==null?void 0:G[$])!=null?Rn:0,Ci=cn?Pn:ot-H[an]-I[an]-Ft+W.altAxis,Fn=cn?ot+H[an]+I[an]-Ft-W.altAxis:ln,Si=y&&cn?As(Ci,ot,Fn):jt(y?Ci:Pn,ot,y?Fn:ln);j[$]=Si,Q[$]=Si-ot}e.modifiersData[i]=Q}}var Pi={name:"preventOverflow",enabled:!0,phase:"main",fn:ku,requiresIfExists:["offset"]};function eo(n){return{scrollLeft:n.scrollLeft,scrollTop:n.scrollTop}}function to(n){return n===ce(n)||!_e(n)?Wt(n):eo(n)}function Hu(n){var e=n.getBoundingClientRect(),t=ct(e.width)/n.offsetWidth||1,i=ct(e.height)/n.offsetHeight||1;return t!==1||i!==1}function no(n,e,t){t===void 0&&(t=!1);var i=_e(e),r=_e(e)&&Hu(e),o=Se(e),s=Ge(n,r,t),a={scrollLeft:0,scrollTop:0},l={x:0,y:0};return(i||!i&&!t)&&((xe(e)!=="body"||Ut(o))&&(a=to(e)),_e(e)?(l=Ge(e,!0),l.x+=e.clientLeft,l.y+=e.clientTop):o&&(l.x=qt(o))),{x:s.left+a.scrollLeft-l.x,y:s.top+a.scrollTop-l.y,width:s.width,height:s.height}}function Iu(n){var e=new Map,t=new Set,i=[];n.forEach(function(o){e.set(o.name,o)});function r(o){t.add(o.name);var s=[].concat(o.requires||[],o.requiresIfExists||[]);s.forEach(function(a){if(!t.has(a)){var l=e.get(a);l&&r(l)}}),i.push(o)}return n.forEach(function(o){t.has(o.name)||r(o)}),i}function io(n){var e=Iu(n);return Yr.reduce(function(t,i){return t.concat(e.filter(function(r){return r.phase===i}))},[])}function ro(n){var e;return function(){return e||(e=new Promise(function(t){Promise.resolve().then(function(){e=void 0,t(n())})})),e}}function oo(n){var e=n.reduce(function(t,i){var r=t[i.name];return t[i.name]=r?Object.assign({},r,i,{options:Object.assign({},r.options,i.options),data:Object.assign({},r.data,i.data)}):i,t},{});return Object.keys(e).map(function(t){return e[t]})}var Ns={placement:"bottom",modifiers:[],strategy:"absolute"};function ks(){for(var n=arguments.length,e=new Array(n),t=0;t(n&&window.CSS&&window.CSS.escape&&(n=n.replace(/#([^\s"#']+)/g,(e,t)=>`#${CSS.escape(t)}`)),n),Bu=n=>n==null?`${n}`:Object.prototype.toString.call(n).match(/\s([a-z]+)/i)[1].toLowerCase(),Vu=n=>{do n+=Math.floor(Math.random()*Fu);while(document.getElementById(n));return n},zu=n=>{if(!n)return 0;let{transitionDuration:e,transitionDelay:t}=window.getComputedStyle(n),i=Number.parseFloat(e),r=Number.parseFloat(t);return!i&&!r?0:(e=e.split(",")[0],t=t.split(",")[0],(Number.parseFloat(e)+Number.parseFloat(t))*$u)},da=n=>{n.dispatchEvent(new Event(xo))},dt=n=>!n||typeof n!="object"?!1:(typeof n.jquery!="undefined"&&(n=n[0]),typeof n.nodeType!="undefined"),xt=n=>dt(n)?n.jquery?n[0]:n:typeof n=="string"&&n.length>0?document.querySelector(ua(n)):null,Cn=n=>{if(!dt(n)||n.getClientRects().length===0)return!1;let e=getComputedStyle(n).getPropertyValue("visibility")==="visible",t=n.closest("details:not([open])");if(!t)return e;if(t!==n){let i=n.closest("summary");if(i&&i.parentNode!==t||i===null)return!1}return e},Tt=n=>!n||n.nodeType!==Node.ELEMENT_NODE||n.classList.contains("disabled")?!0:typeof n.disabled!="undefined"?n.disabled:n.hasAttribute("disabled")&&n.getAttribute("disabled")!=="false",fa=n=>{if(!document.documentElement.attachShadow)return null;if(typeof n.getRootNode=="function"){let e=n.getRootNode();return e instanceof ShadowRoot?e:null}return n instanceof ShadowRoot?n:n.parentNode?fa(n.parentNode):null},Yi=()=>{},ei=n=>{n.offsetHeight},ha=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,ao=[],ju=n=>{document.readyState==="loading"?(ao.length||document.addEventListener("DOMContentLoaded",()=>{for(let e of ao)e()}),ao.push(n)):n()},Xe=()=>document.documentElement.dir==="rtl",Je=n=>{ju(()=>{let e=ha();if(e){let t=n.NAME,i=e.fn[t];e.fn[t]=n.jQueryInterface,e.fn[t].Constructor=n,e.fn[t].noConflict=()=>(e.fn[t]=i,n.jQueryInterface)}})},Pe=(n,e=[],t=n)=>typeof n=="function"?n.call(...e):t,pa=(n,e,t=!0)=>{if(!t){Pe(n);return}let r=zu(e)+5,o=!1,s=({target:a})=>{a===e&&(o=!0,e.removeEventListener(xo,s),Pe(n))};e.addEventListener(xo,s),setTimeout(()=>{o||da(e)},r)},Ao=(n,e,t,i)=>{let r=n.length,o=n.indexOf(e);return o===-1?!t&&i?n[r-1]:n[0]:(o+=t?1:-1,i&&(o=(o+r)%r),n[Math.max(0,Math.min(o,r-1))])},Wu=/[^.]*(?=\..*)\.|.*/,qu=/\..*/,Uu=/::\d+$/,lo={},Rs=1,ma={mouseenter:"mouseover",mouseleave:"mouseout"},Yu=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function ga(n,e){return e&&`${e}::${Rs++}`||n.uidEvent||Rs++}function va(n){let e=ga(n);return n.uidEvent=e,lo[e]=lo[e]||{},lo[e]}function Gu(n,e){return function t(i){return Do(i,{delegateTarget:n}),t.oneOff&&x.off(n,i.type,e),e.apply(n,[i])}}function Ku(n,e,t){return function i(r){let o=n.querySelectorAll(e);for(let{target:s}=r;s&&s!==this;s=s.parentNode)for(let a of o)if(a===s)return Do(r,{delegateTarget:s}),i.oneOff&&x.off(n,r.type,e,t),t.apply(s,[r])}}function ya(n,e,t=null){return Object.values(n).find(i=>i.callable===e&&i.delegationSelector===t)}function Ea(n,e,t){let i=typeof e=="string",r=i?t:e||t,o=ba(n);return Yu.has(o)||(o=n),[i,r,o]}function Ps(n,e,t,i,r){if(typeof e!="string"||!n)return;let[o,s,a]=Ea(e,t,i);e in ma&&(s=(m=>function(g){if(!g.relatedTarget||g.relatedTarget!==g.delegateTarget&&!g.delegateTarget.contains(g.relatedTarget))return m.call(this,g)})(s));let l=va(n),c=l[a]||(l[a]={}),u=ya(c,s,o?t:null);if(u){u.oneOff=u.oneOff&&r;return}let d=ga(s,e.replace(Wu,"")),p=o?Ku(n,t,s):Gu(n,s);p.delegationSelector=o?t:null,p.callable=s,p.oneOff=r,p.uidEvent=d,c[d]=p,n.addEventListener(a,p,o)}function To(n,e,t,i,r){let o=ya(e[t],i,r);o&&(n.removeEventListener(t,o,!!r),delete e[t][o.uidEvent])}function Xu(n,e,t,i){let r=e[t]||{};for(let[o,s]of Object.entries(r))o.includes(i)&&To(n,e,t,s.callable,s.delegationSelector)}function ba(n){return n=n.replace(qu,""),ma[n]||n}var x={on(n,e,t,i){Ps(n,e,t,i,!1)},one(n,e,t,i){Ps(n,e,t,i,!0)},off(n,e,t,i){if(typeof e!="string"||!n)return;let[r,o,s]=Ea(e,t,i),a=s!==e,l=va(n),c=l[s]||{},u=e.startsWith(".");if(typeof o!="undefined"){if(!Object.keys(c).length)return;To(n,l,s,o,r?t:null);return}if(u)for(let d of Object.keys(l))Xu(n,l,d,e.slice(1));for(let[d,p]of Object.entries(c)){let y=d.replace(Uu,"");(!a||e.includes(y))&&To(n,l,s,p.callable,p.delegationSelector)}},trigger(n,e,t){if(typeof e!="string"||!n)return null;let i=ha(),r=ba(e),o=e!==r,s=null,a=!0,l=!0,c=!1;o&&i&&(s=i.Event(e,t),i(n).trigger(s),a=!s.isPropagationStopped(),l=!s.isImmediatePropagationStopped(),c=s.isDefaultPrevented());let u=Do(new Event(e,{bubbles:a,cancelable:!0}),t);return c&&u.preventDefault(),l&&n.dispatchEvent(u),u.defaultPrevented&&s&&s.preventDefault(),u}};function Do(n,e={}){for(let[t,i]of Object.entries(e))try{n[t]=i}catch(r){Object.defineProperty(n,t,{configurable:!0,get(){return i}})}return n}function Fs(n){if(n==="true")return!0;if(n==="false")return!1;if(n===Number(n).toString())return Number(n);if(n===""||n==="null")return null;if(typeof n!="string")return n;try{return JSON.parse(decodeURIComponent(n))}catch(e){return n}}function co(n){return n.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}var ft={setDataAttribute(n,e,t){n.setAttribute(`data-bs-${co(e)}`,t)},removeDataAttribute(n,e){n.removeAttribute(`data-bs-${co(e)}`)},getDataAttributes(n){if(!n)return{};let e={},t=Object.keys(n.dataset).filter(i=>i.startsWith("bs")&&!i.startsWith("bsConfig"));for(let i of t){let r=i.replace(/^bs/,"");r=r.charAt(0).toLowerCase()+r.slice(1),e[r]=Fs(n.dataset[i])}return e},getDataAttribute(n,e){return Fs(n.getAttribute(`data-bs-${co(e)}`))}},Xt=class{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(e){return e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e}_mergeConfigObj(e,t){let i=dt(t)?ft.getDataAttribute(t,"config"):{};return D(D(D(D({},this.constructor.Default),typeof i=="object"?i:{}),dt(t)?ft.getDataAttributes(t):{}),typeof e=="object"?e:{})}_typeCheckConfig(e,t=this.constructor.DefaultType){for(let[i,r]of Object.entries(t)){let o=e[i],s=dt(o)?"element":Bu(o);if(!new RegExp(r).test(s))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${i}" provided type "${s}" but expected type "${r}".`)}}},Qu="5.3.8",qe=class extends Xt{constructor(e,t){super(),e=xt(e),e&&(this._element=e,this._config=this._getConfig(t),so.set(this._element,this.constructor.DATA_KEY,this))}dispose(){so.remove(this._element,this.constructor.DATA_KEY),x.off(this._element,this.constructor.EVENT_KEY);for(let e of Object.getOwnPropertyNames(this))this[e]=null}_queueCallback(e,t,i=!0){pa(e,t,i)}_getConfig(e){return e=this._mergeConfigObj(e,this._element),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}static getInstance(e){return so.get(xt(e),this.DATA_KEY)}static getOrCreateInstance(e,t={}){return this.getInstance(e)||new this(e,typeof t=="object"?t:null)}static get VERSION(){return Qu}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(e){return`${e}${this.EVENT_KEY}`}},uo=n=>{let e=n.getAttribute("data-bs-target");if(!e||e==="#"){let t=n.getAttribute("href");if(!t||!t.includes("#")&&!t.startsWith("."))return null;t.includes("#")&&!t.startsWith("#")&&(t=`#${t.split("#")[1]}`),e=t&&t!=="#"?t.trim():null}return e?e.split(",").map(t=>ua(t)).join(","):null},z={find(n,e=document.documentElement){return[].concat(...Element.prototype.querySelectorAll.call(e,n))},findOne(n,e=document.documentElement){return Element.prototype.querySelector.call(e,n)},children(n,e){return[].concat(...n.children).filter(t=>t.matches(e))},parents(n,e){let t=[],i=n.parentNode.closest(e);for(;i;)t.push(i),i=i.parentNode.closest(e);return t},prev(n,e){let t=n.previousElementSibling;for(;t;){if(t.matches(e))return[t];t=t.previousElementSibling}return[]},next(n,e){let t=n.nextElementSibling;for(;t;){if(t.matches(e))return[t];t=t.nextElementSibling}return[]},focusableChildren(n){let e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map(t=>`${t}:not([tabindex^="-"])`).join(",");return this.find(e,n).filter(t=>!Tt(t)&&Cn(t))},getSelectorFromElement(n){let e=uo(n);return e&&z.findOne(e)?e:null},getElementFromSelector(n){let e=uo(n);return e?z.findOne(e):null},getMultipleElementsFromSelector(n){let e=uo(n);return e?z.find(e):[]}},tr=(n,e="hide")=>{let t=`click.dismiss${n.EVENT_KEY}`,i=n.NAME;x.on(document,t,`[data-bs-dismiss="${i}"]`,function(r){if(["A","AREA"].includes(this.tagName)&&r.preventDefault(),Tt(this))return;let o=z.getElementFromSelector(this)||this.closest(`.${i}`);n.getOrCreateInstance(o)[e]()})},Ju="alert",Zu="bs.alert",_a=`.${Zu}`,ed=`close${_a}`,td=`closed${_a}`,nd="fade",id="show",Gi=class n extends qe{static get NAME(){return Ju}close(){if(x.trigger(this._element,ed).defaultPrevented)return;this._element.classList.remove(id);let t=this._element.classList.contains(nd);this._queueCallback(()=>this._destroyElement(),this._element,t)}_destroyElement(){this._element.remove(),x.trigger(this._element,td),this.dispose()}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e](this)}})}};tr(Gi,"close");Je(Gi);var rd="button",od="bs.button",sd=`.${od}`,ad=".data-api",ld="active",$s='[data-bs-toggle="button"]',cd=`click${sd}${ad}`,Ki=class n extends qe{static get NAME(){return rd}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle(ld))}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this);e==="toggle"&&t[e]()})}};x.on(document,cd,$s,n=>{n.preventDefault();let e=n.target.closest($s);Ki.getOrCreateInstance(e).toggle()});Je(Ki);var ud="swipe",Sn=".bs.swipe",dd=`touchstart${Sn}`,fd=`touchmove${Sn}`,hd=`touchend${Sn}`,pd=`pointerdown${Sn}`,md=`pointerup${Sn}`,gd="touch",vd="pen",yd="pointer-event",Ed=40,bd={endCallback:null,leftCallback:null,rightCallback:null},_d={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"},Xi=class n extends Xt{constructor(e,t){super(),this._element=e,!(!e||!n.isSupported())&&(this._config=this._getConfig(t),this._deltaX=0,this._supportPointerEvents=!!window.PointerEvent,this._initEvents())}static get Default(){return bd}static get DefaultType(){return _d}static get NAME(){return ud}dispose(){x.off(this._element,Sn)}_start(e){if(!this._supportPointerEvents){this._deltaX=e.touches[0].clientX;return}this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX)}_end(e){this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX-this._deltaX),this._handleSwipe(),Pe(this._config.endCallback)}_move(e){this._deltaX=e.touches&&e.touches.length>1?0:e.touches[0].clientX-this._deltaX}_handleSwipe(){let e=Math.abs(this._deltaX);if(e<=Ed)return;let t=e/this._deltaX;this._deltaX=0,t&&Pe(t>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(x.on(this._element,pd,e=>this._start(e)),x.on(this._element,md,e=>this._end(e)),this._element.classList.add(yd)):(x.on(this._element,dd,e=>this._start(e)),x.on(this._element,fd,e=>this._move(e)),x.on(this._element,hd,e=>this._end(e)))}_eventIsPointerPenTouch(e){return this._supportPointerEvents&&(e.pointerType===vd||e.pointerType===gd)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}},wd="carousel",xd="bs.carousel",Dt=`.${xd}`,wa=".data-api",Td="ArrowLeft",Cd="ArrowRight",Sd=500,Xn="next",En="prev",_n="left",qi="right",Ad=`slide${Dt}`,fo=`slid${Dt}`,Dd=`keydown${Dt}`,Od=`mouseenter${Dt}`,Ld=`mouseleave${Dt}`,Md=`dragstart${Dt}`,Nd=`load${Dt}${wa}`,kd=`click${Dt}${wa}`,xa="carousel",$i="active",Hd="slide",Id="carousel-item-end",Rd="carousel-item-start",Pd="carousel-item-next",Fd="carousel-item-prev",Ta=".active",Ca=".carousel-item",$d=Ta+Ca,Bd=".carousel-item img",Vd=".carousel-indicators",zd="[data-bs-slide], [data-bs-slide-to]",jd='[data-bs-ride="carousel"]',Wd={[Td]:qi,[Cd]:_n},qd={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ud={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"},Jn=class n extends qe{constructor(e,t){super(e,t),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(Vd,this._element),this._addEventListeners(),this._config.ride===xa&&this.cycle()}static get Default(){return qd}static get DefaultType(){return Ud}static get NAME(){return wd}next(){this._slide(Xn)}nextWhenVisible(){!document.hidden&&Cn(this._element)&&this.next()}prev(){this._slide(En)}pause(){this._isSliding&&da(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval(()=>this.nextWhenVisible(),this._config.interval)}_maybeEnableCycle(){if(this._config.ride){if(this._isSliding){x.one(this._element,fo,()=>this.cycle());return}this.cycle()}}to(e){let t=this._getItems();if(e>t.length-1||e<0)return;if(this._isSliding){x.one(this._element,fo,()=>this.to(e));return}let i=this._getItemIndex(this._getActive());if(i===e)return;let r=e>i?Xn:En;this._slide(r,t[e])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(e){return e.defaultInterval=e.interval,e}_addEventListeners(){this._config.keyboard&&x.on(this._element,Dd,e=>this._keydown(e)),this._config.pause==="hover"&&(x.on(this._element,Od,()=>this.pause()),x.on(this._element,Ld,()=>this._maybeEnableCycle())),this._config.touch&&Xi.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(let i of z.find(Bd,this._element))x.on(i,Md,r=>r.preventDefault());let t={leftCallback:()=>this._slide(this._directionToOrder(_n)),rightCallback:()=>this._slide(this._directionToOrder(qi)),endCallback:()=>{this._config.pause==="hover"&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),Sd+this._config.interval))}};this._swipeHelper=new Xi(this._element,t)}_keydown(e){if(/input|textarea/i.test(e.target.tagName))return;let t=Wd[e.key];t&&(e.preventDefault(),this._slide(this._directionToOrder(t)))}_getItemIndex(e){return this._getItems().indexOf(e)}_setActiveIndicatorElement(e){if(!this._indicatorsElement)return;let t=z.findOne(Ta,this._indicatorsElement);t.classList.remove($i),t.removeAttribute("aria-current");let i=z.findOne(`[data-bs-slide-to="${e}"]`,this._indicatorsElement);i&&(i.classList.add($i),i.setAttribute("aria-current","true"))}_updateInterval(){let e=this._activeElement||this._getActive();if(!e)return;let t=Number.parseInt(e.getAttribute("data-bs-interval"),10);this._config.interval=t||this._config.defaultInterval}_slide(e,t=null){if(this._isSliding)return;let i=this._getActive(),r=e===Xn,o=t||Ao(this._getItems(),i,r,this._config.wrap);if(o===i)return;let s=this._getItemIndex(o),a=y=>x.trigger(this._element,y,{relatedTarget:o,direction:this._orderToDirection(e),from:this._getItemIndex(i),to:s});if(a(Ad).defaultPrevented||!i||!o)return;let c=!!this._interval;this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(s),this._activeElement=o;let u=r?Rd:Id,d=r?Pd:Fd;o.classList.add(d),ei(o),i.classList.add(u),o.classList.add(u);let p=()=>{o.classList.remove(u,d),o.classList.add($i),i.classList.remove($i,d,u),this._isSliding=!1,a(fo)};this._queueCallback(p,i,this._isAnimated()),c&&this.cycle()}_isAnimated(){return this._element.classList.contains(Hd)}_getActive(){return z.findOne($d,this._element)}_getItems(){return z.find(Ca,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(e){return Xe()?e===_n?En:Xn:e===_n?Xn:En}_orderToDirection(e){return Xe()?e===En?_n:qi:e===En?qi:_n}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="number"){t.to(e);return}if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e]()}})}};x.on(document,kd,zd,function(n){let e=z.getElementFromSelector(this);if(!e||!e.classList.contains(xa))return;n.preventDefault();let t=Jn.getOrCreateInstance(e),i=this.getAttribute("data-bs-slide-to");if(i){t.to(i),t._maybeEnableCycle();return}if(ft.getDataAttribute(this,"slide")==="next"){t.next(),t._maybeEnableCycle();return}t.prev(),t._maybeEnableCycle()});x.on(window,Nd,()=>{let n=z.find(jd);for(let e of n)Jn.getOrCreateInstance(e)});Je(Jn);var Yd="collapse",Gd="bs.collapse",ti=`.${Gd}`,Kd=".data-api",Xd=`show${ti}`,Qd=`shown${ti}`,Jd=`hide${ti}`,Zd=`hidden${ti}`,ef=`click${ti}${Kd}`,ho="show",xn="collapse",Bi="collapsing",tf="collapsed",nf=`:scope .${xn} .${xn}`,rf="collapse-horizontal",of="width",sf="height",af=".collapse.show, .collapse.collapsing",Co='[data-bs-toggle="collapse"]',lf={parent:null,toggle:!0},cf={parent:"(null|element)",toggle:"boolean"},Ct=class n extends qe{constructor(e,t){super(e,t),this._isTransitioning=!1,this._triggerArray=[];let i=z.find(Co);for(let r of i){let o=z.getSelectorFromElement(r),s=z.find(o).filter(a=>a===this._element);o!==null&&s.length&&this._triggerArray.push(r)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return lf}static get DefaultType(){return cf}static get NAME(){return Yd}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let e=[];if(this._config.parent&&(e=this._getFirstLevelChildren(af).filter(a=>a!==this._element).map(a=>n.getOrCreateInstance(a,{toggle:!1}))),e.length&&e[0]._isTransitioning||x.trigger(this._element,Xd).defaultPrevented)return;for(let a of e)a.hide();let i=this._getDimension();this._element.classList.remove(xn),this._element.classList.add(Bi),this._element.style[i]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;let r=()=>{this._isTransitioning=!1,this._element.classList.remove(Bi),this._element.classList.add(xn,ho),this._element.style[i]="",x.trigger(this._element,Qd)},s=`scroll${i[0].toUpperCase()+i.slice(1)}`;this._queueCallback(r,this._element,!0),this._element.style[i]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown()||x.trigger(this._element,Jd).defaultPrevented)return;let t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,ei(this._element),this._element.classList.add(Bi),this._element.classList.remove(xn,ho);for(let r of this._triggerArray){let o=z.getElementFromSelector(r);o&&!this._isShown(o)&&this._addAriaAndCollapsedClass([r],!1)}this._isTransitioning=!0;let i=()=>{this._isTransitioning=!1,this._element.classList.remove(Bi),this._element.classList.add(xn),x.trigger(this._element,Zd)};this._element.style[t]="",this._queueCallback(i,this._element,!0)}_isShown(e=this._element){return e.classList.contains(ho)}_configAfterMerge(e){return e.toggle=!!e.toggle,e.parent=xt(e.parent),e}_getDimension(){return this._element.classList.contains(rf)?of:sf}_initializeChildren(){if(!this._config.parent)return;let e=this._getFirstLevelChildren(Co);for(let t of e){let i=z.getElementFromSelector(t);i&&this._addAriaAndCollapsedClass([t],this._isShown(i))}}_getFirstLevelChildren(e){let t=z.find(nf,this._config.parent);return z.find(e,this._config.parent).filter(i=>!t.includes(i))}_addAriaAndCollapsedClass(e,t){if(e.length)for(let i of e)i.classList.toggle(tf,!t),i.setAttribute("aria-expanded",t)}static jQueryInterface(e){let t={};return typeof e=="string"&&/show|hide/.test(e)&&(t.toggle=!1),this.each(function(){let i=n.getOrCreateInstance(this,t);if(typeof e=="string"){if(typeof i[e]=="undefined")throw new TypeError(`No method named "${e}"`);i[e]()}})}};x.on(document,ef,Co,function(n){(n.target.tagName==="A"||n.delegateTarget&&n.delegateTarget.tagName==="A")&&n.preventDefault();for(let e of z.getMultipleElementsFromSelector(this))Ct.getOrCreateInstance(e,{toggle:!1}).toggle()});Je(Ct);var Bs="dropdown",uf="bs.dropdown",Jt=`.${uf}`,Oo=".data-api",df="Escape",Vs="Tab",ff="ArrowUp",zs="ArrowDown",hf=2,pf=`hide${Jt}`,mf=`hidden${Jt}`,gf=`show${Jt}`,vf=`shown${Jt}`,Sa=`click${Jt}${Oo}`,Aa=`keydown${Jt}${Oo}`,yf=`keyup${Jt}${Oo}`,wn="show",Ef="dropup",bf="dropend",_f="dropstart",wf="dropup-center",xf="dropdown-center",Gt='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Tf=`${Gt}.${wn}`,Ui=".dropdown-menu",Cf=".navbar",Sf=".navbar-nav",Af=".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",Df=Xe()?"top-end":"top-start",Of=Xe()?"top-start":"top-end",Lf=Xe()?"bottom-end":"bottom-start",Mf=Xe()?"bottom-start":"bottom-end",Nf=Xe()?"left-start":"right-start",kf=Xe()?"right-start":"left-start",Hf="top",If="bottom",Rf={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Pf={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"},St=class n extends qe{constructor(e,t){super(e,t),this._popper=null,this._parent=this._element.parentNode,this._menu=z.next(this._element,Ui)[0]||z.prev(this._element,Ui)[0]||z.findOne(Ui,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Rf}static get DefaultType(){return Pf}static get NAME(){return Bs}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Tt(this._element)||this._isShown())return;let e={relatedTarget:this._element};if(!x.trigger(this._element,gf,e).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(Sf))for(let i of[].concat(...document.body.children))x.on(i,"mouseover",Yi);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(wn),this._element.classList.add(wn),x.trigger(this._element,vf,e)}}hide(){if(Tt(this._element)||!this._isShown())return;let e={relatedTarget:this._element};this._completeHide(e)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(e){if(!x.trigger(this._element,pf,e).defaultPrevented){if("ontouchstart"in document.documentElement)for(let i of[].concat(...document.body.children))x.off(i,"mouseover",Yi);this._popper&&this._popper.destroy(),this._menu.classList.remove(wn),this._element.classList.remove(wn),this._element.setAttribute("aria-expanded","false"),ft.removeDataAttribute(this._menu,"popper"),x.trigger(this._element,mf,e)}}_getConfig(e){if(e=super._getConfig(e),typeof e.reference=="object"&&!dt(e.reference)&&typeof e.reference.getBoundingClientRect!="function")throw new TypeError(`${Bs.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return e}_createPopper(){if(typeof Fi=="undefined")throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org/docs/v2/)");let e=this._element;this._config.reference==="parent"?e=this._parent:dt(this._config.reference)?e=xt(this._config.reference):typeof this._config.reference=="object"&&(e=this._config.reference);let t=this._getPopperConfig();this._popper=Kn(e,this._menu,t)}_isShown(){return this._menu.classList.contains(wn)}_getPlacement(){let e=this._parent;if(e.classList.contains(bf))return Nf;if(e.classList.contains(_f))return kf;if(e.classList.contains(wf))return Hf;if(e.classList.contains(xf))return If;let t=getComputedStyle(this._menu).getPropertyValue("--bs-position").trim()==="end";return e.classList.contains(Ef)?t?Of:Df:t?Mf:Lf}_detectNavbar(){return this._element.closest(Cf)!==null}_getOffset(){let{offset:e}=this._config;return typeof e=="string"?e.split(",").map(t=>Number.parseInt(t,10)):typeof e=="function"?t=>e(t,this._element):e}_getPopperConfig(){let e={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||this._config.display==="static")&&(ft.setDataAttribute(this._menu,"popper","static"),e.modifiers=[{name:"applyStyles",enabled:!1}]),D(D({},e),Pe(this._config.popperConfig,[void 0,e]))}_selectMenuItem({key:e,target:t}){let i=z.find(Af,this._menu).filter(r=>Cn(r));i.length&&Ao(i,t,e===zs,!i.includes(t)).focus()}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e]()}})}static clearMenus(e){if(e.button===hf||e.type==="keyup"&&e.key!==Vs)return;let t=z.find(Tf);for(let i of t){let r=n.getInstance(i);if(!r||r._config.autoClose===!1)continue;let o=e.composedPath(),s=o.includes(r._menu);if(o.includes(r._element)||r._config.autoClose==="inside"&&!s||r._config.autoClose==="outside"&&s||r._menu.contains(e.target)&&(e.type==="keyup"&&e.key===Vs||/input|select|option|textarea|form/i.test(e.target.tagName)))continue;let a={relatedTarget:r._element};e.type==="click"&&(a.clickEvent=e),r._completeHide(a)}}static dataApiKeydownHandler(e){let t=/input|textarea/i.test(e.target.tagName),i=e.key===df,r=[ff,zs].includes(e.key);if(!r&&!i||t&&!i)return;e.preventDefault();let o=this.matches(Gt)?this:z.prev(this,Gt)[0]||z.next(this,Gt)[0]||z.findOne(Gt,e.delegateTarget.parentNode),s=n.getOrCreateInstance(o);if(r){e.stopPropagation(),s.show(),s._selectMenuItem(e);return}s._isShown()&&(e.stopPropagation(),s.hide(),o.focus())}};x.on(document,Aa,Gt,St.dataApiKeydownHandler);x.on(document,Aa,Ui,St.dataApiKeydownHandler);x.on(document,Sa,St.clearMenus);x.on(document,yf,St.clearMenus);x.on(document,Sa,Gt,function(n){n.preventDefault(),St.getOrCreateInstance(this).toggle()});Je(St);var Da="backdrop",Ff="fade",js="show",Ws=`mousedown.bs.${Da}`,$f={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Bf={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"},Qi=class extends Xt{constructor(e){super(),this._config=this._getConfig(e),this._isAppended=!1,this._element=null}static get Default(){return $f}static get DefaultType(){return Bf}static get NAME(){return Da}show(e){if(!this._config.isVisible){Pe(e);return}this._append();let t=this._getElement();this._config.isAnimated&&ei(t),t.classList.add(js),this._emulateAnimation(()=>{Pe(e)})}hide(e){if(!this._config.isVisible){Pe(e);return}this._getElement().classList.remove(js),this._emulateAnimation(()=>{this.dispose(),Pe(e)})}dispose(){this._isAppended&&(x.off(this._element,Ws),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){let e=document.createElement("div");e.className=this._config.className,this._config.isAnimated&&e.classList.add(Ff),this._element=e}return this._element}_configAfterMerge(e){return e.rootElement=xt(e.rootElement),e}_append(){if(this._isAppended)return;let e=this._getElement();this._config.rootElement.append(e),x.on(e,Ws,()=>{Pe(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(e){pa(e,this._getElement(),this._config.isAnimated)}},Vf="focustrap",zf="bs.focustrap",Ji=`.${zf}`,jf=`focusin${Ji}`,Wf=`keydown.tab${Ji}`,qf="Tab",Uf="forward",qs="backward",Yf={autofocus:!0,trapElement:null},Gf={autofocus:"boolean",trapElement:"element"},Zi=class extends Xt{constructor(e){super(),this._config=this._getConfig(e),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return Yf}static get DefaultType(){return Gf}static get NAME(){return Vf}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),x.off(document,Ji),x.on(document,jf,e=>this._handleFocusin(e)),x.on(document,Wf,e=>this._handleKeydown(e)),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,x.off(document,Ji))}_handleFocusin(e){let{trapElement:t}=this._config;if(e.target===document||e.target===t||t.contains(e.target))return;let i=z.focusableChildren(t);i.length===0?t.focus():this._lastTabNavDirection===qs?i[i.length-1].focus():i[0].focus()}_handleKeydown(e){e.key===qf&&(this._lastTabNavDirection=e.shiftKey?qs:Uf)}},Us=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Ys=".sticky-top",Vi="padding-right",Gs="margin-right",Zn=class{constructor(){this._element=document.body}getWidth(){let e=document.documentElement.clientWidth;return Math.abs(window.innerWidth-e)}hide(){let e=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Vi,t=>t+e),this._setElementAttributes(Us,Vi,t=>t+e),this._setElementAttributes(Ys,Gs,t=>t-e)}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Vi),this._resetElementAttributes(Us,Vi),this._resetElementAttributes(Ys,Gs)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(e,t,i){let r=this.getWidth(),o=s=>{if(s!==this._element&&window.innerWidth>s.clientWidth+r)return;this._saveInitialAttribute(s,t);let a=window.getComputedStyle(s).getPropertyValue(t);s.style.setProperty(t,`${i(Number.parseFloat(a))}px`)};this._applyManipulationCallback(e,o)}_saveInitialAttribute(e,t){let i=e.style.getPropertyValue(t);i&&ft.setDataAttribute(e,t,i)}_resetElementAttributes(e,t){let i=r=>{let o=ft.getDataAttribute(r,t);if(o===null){r.style.removeProperty(t);return}ft.removeDataAttribute(r,t),r.style.setProperty(t,o)};this._applyManipulationCallback(e,i)}_applyManipulationCallback(e,t){if(dt(e)){t(e);return}for(let i of z.find(e,this._element))t(i)}},Kf="modal",Xf="bs.modal",Qe=`.${Xf}`,Qf=".data-api",Jf="Escape",Zf=`hide${Qe}`,eh=`hidePrevented${Qe}`,Oa=`hidden${Qe}`,La=`show${Qe}`,th=`shown${Qe}`,nh=`resize${Qe}`,ih=`click.dismiss${Qe}`,rh=`mousedown.dismiss${Qe}`,oh=`keydown.dismiss${Qe}`,sh=`click${Qe}${Qf}`,Ks="modal-open",ah="fade",Xs="show",po="modal-static",lh=".modal.show",ch=".modal-dialog",uh=".modal-body",dh='[data-bs-toggle="modal"]',fh={backdrop:!0,focus:!0,keyboard:!0},hh={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"},tt=class n extends qe{constructor(e,t){super(e,t),this._dialog=z.findOne(ch,this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Zn,this._addEventListeners()}static get Default(){return fh}static get DefaultType(){return hh}static get NAME(){return Kf}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){this._isShown||this._isTransitioning||x.trigger(this._element,La,{relatedTarget:e}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Ks),this._adjustDialog(),this._backdrop.show(()=>this._showElement(e)))}hide(){!this._isShown||this._isTransitioning||x.trigger(this._element,Zf).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Xs),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated()))}dispose(){x.off(window,Qe),x.off(this._dialog,Qe),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Qi({isVisible:!!this._config.backdrop,isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Zi({trapElement:this._element})}_showElement(e){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;let t=z.findOne(uh,this._dialog);t&&(t.scrollTop=0),ei(this._element),this._element.classList.add(Xs);let i=()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,x.trigger(this._element,th,{relatedTarget:e})};this._queueCallback(i,this._dialog,this._isAnimated())}_addEventListeners(){x.on(this._element,oh,e=>{if(e.key===Jf){if(this._config.keyboard){this.hide();return}this._triggerBackdropTransition()}}),x.on(window,nh,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),x.on(this._element,rh,e=>{x.one(this._element,ih,t=>{if(!(this._element!==e.target||this._element!==t.target)){if(this._config.backdrop==="static"){this._triggerBackdropTransition();return}this._config.backdrop&&this.hide()}})})}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove(Ks),this._resetAdjustments(),this._scrollBar.reset(),x.trigger(this._element,Oa)})}_isAnimated(){return this._element.classList.contains(ah)}_triggerBackdropTransition(){if(x.trigger(this._element,eh).defaultPrevented)return;let t=this._element.scrollHeight>document.documentElement.clientHeight,i=this._element.style.overflowY;i==="hidden"||this._element.classList.contains(po)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(po),this._queueCallback(()=>{this._element.classList.remove(po),this._queueCallback(()=>{this._element.style.overflowY=i},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){let e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._scrollBar.getWidth(),i=t>0;if(i&&!e){let r=Xe()?"paddingLeft":"paddingRight";this._element.style[r]=`${t}px`}if(!i&&e){let r=Xe()?"paddingRight":"paddingLeft";this._element.style[r]=`${t}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(e,t){return this.each(function(){let i=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof i[e]=="undefined")throw new TypeError(`No method named "${e}"`);i[e](t)}})}};x.on(document,sh,dh,function(n){let e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&n.preventDefault(),x.one(e,La,r=>{r.defaultPrevented||x.one(e,Oa,()=>{Cn(this)&&this.focus()})});let t=z.findOne(lh);t&&tt.getInstance(t).hide(),tt.getOrCreateInstance(e).toggle(this)});tr(tt);Je(tt);var ph="offcanvas",mh="bs.offcanvas",mt=`.${mh}`,Ma=".data-api",gh=`load${mt}${Ma}`,vh="Escape",Qs="show",Js="showing",Zs="hiding",yh="offcanvas-backdrop",Na=".offcanvas.show",Eh=`show${mt}`,bh=`shown${mt}`,_h=`hide${mt}`,ea=`hidePrevented${mt}`,ka=`hidden${mt}`,wh=`resize${mt}`,xh=`click${mt}${Ma}`,Th=`keydown.dismiss${mt}`,Ch='[data-bs-toggle="offcanvas"]',Sh={backdrop:!0,keyboard:!0,scroll:!1},Ah={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"},At=class n extends qe{constructor(e,t){super(e,t),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Sh}static get DefaultType(){return Ah}static get NAME(){return ph}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){if(this._isShown||x.trigger(this._element,Eh,{relatedTarget:e}).defaultPrevented)return;this._isShown=!0,this._backdrop.show(),this._config.scroll||new Zn().hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Js);let i=()=>{(!this._config.scroll||this._config.backdrop)&&this._focustrap.activate(),this._element.classList.add(Qs),this._element.classList.remove(Js),x.trigger(this._element,bh,{relatedTarget:e})};this._queueCallback(i,this._element,!0)}hide(){if(!this._isShown||x.trigger(this._element,_h).defaultPrevented)return;this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Zs),this._backdrop.hide();let t=()=>{this._element.classList.remove(Qs,Zs),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||new Zn().reset(),x.trigger(this._element,ka)};this._queueCallback(t,this._element,!0)}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){let e=()=>{if(this._config.backdrop==="static"){x.trigger(this._element,ea);return}this.hide()},t=!!this._config.backdrop;return new Qi({className:yh,isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?e:null})}_initializeFocusTrap(){return new Zi({trapElement:this._element})}_addEventListeners(){x.on(this._element,Th,e=>{if(e.key===vh){if(this._config.keyboard){this.hide();return}x.trigger(this._element,ea)}})}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e](this)}})}};x.on(document,xh,Ch,function(n){let e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&n.preventDefault(),Tt(this))return;x.one(e,ka,()=>{Cn(this)&&this.focus()});let t=z.findOne(Na);t&&t!==e&&At.getInstance(t).hide(),At.getOrCreateInstance(e).toggle(this)});x.on(window,gh,()=>{for(let n of z.find(Na))At.getOrCreateInstance(n).show()});x.on(window,wh,()=>{for(let n of z.find("[aria-modal][class*=show][class*=offcanvas-]"))getComputedStyle(n).position!=="fixed"&&At.getOrCreateInstance(n).hide()});tr(At);Je(At);var Dh=/^aria-[\w-]*$/i,Ha={"*":["class","dir","id","lang","role",Dh],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Oh=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Lh=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Mh=(n,e)=>{let t=n.nodeName.toLowerCase();return e.includes(t)?Oh.has(t)?!!Lh.test(n.nodeValue):!0:e.filter(i=>i instanceof RegExp).some(i=>i.test(t))};function Nh(n,e,t){if(!n.length)return n;if(t&&typeof t=="function")return t(n);let r=new window.DOMParser().parseFromString(n,"text/html"),o=[].concat(...r.body.querySelectorAll("*"));for(let s of o){let a=s.nodeName.toLowerCase();if(!Object.keys(e).includes(a)){s.remove();continue}let l=[].concat(...s.attributes),c=[].concat(e["*"]||[],e[a]||[]);for(let u of l)Mh(u,c)||s.removeAttribute(u.nodeName)}return r.body.innerHTML}var kh="TemplateFactory",Hh={allowList:Ha,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Ih={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Rh={entry:"(string|element|function|null)",selector:"(string|element)"},So=class extends Xt{constructor(e){super(),this._config=this._getConfig(e)}static get Default(){return Hh}static get DefaultType(){return Ih}static get NAME(){return kh}getContent(){return Object.values(this._config.content).map(e=>this._resolvePossibleFunction(e)).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(e){return this._checkContent(e),this._config.content=D(D({},this._config.content),e),this}toHtml(){let e=document.createElement("div");e.innerHTML=this._maybeSanitize(this._config.template);for(let[r,o]of Object.entries(this._config.content))this._setContent(e,o,r);let t=e.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&t.classList.add(...i.split(" ")),t}_typeCheckConfig(e){super._typeCheckConfig(e),this._checkContent(e.content)}_checkContent(e){for(let[t,i]of Object.entries(e))super._typeCheckConfig({selector:t,entry:i},Rh)}_setContent(e,t,i){let r=z.findOne(i,e);if(r){if(t=this._resolvePossibleFunction(t),!t){r.remove();return}if(dt(t)){this._putElementInTemplate(xt(t),r);return}if(this._config.html){r.innerHTML=this._maybeSanitize(t);return}r.textContent=t}}_maybeSanitize(e){return this._config.sanitize?Nh(e,this._config.allowList,this._config.sanitizeFn):e}_resolvePossibleFunction(e){return Pe(e,[void 0,this])}_putElementInTemplate(e,t){if(this._config.html){t.innerHTML="",t.append(e);return}t.textContent=e.textContent}},Ph="tooltip",Fh=new Set(["sanitize","allowList","sanitizeFn"]),mo="fade",$h="modal",zi="show",Bh=".tooltip-inner",ta=`.${$h}`,na="hide.bs.modal",Qn="hover",go="focus",vo="click",Vh="manual",zh="hide",jh="hidden",Wh="show",qh="shown",Uh="inserted",Yh="click",Gh="focusin",Kh="focusout",Xh="mouseenter",Qh="mouseleave",Jh={AUTO:"auto",TOP:"top",RIGHT:Xe()?"left":"right",BOTTOM:"bottom",LEFT:Xe()?"right":"left"},Zh={allowList:Ha,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ep={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"},ht=class n extends qe{constructor(e,t){if(typeof Fi=="undefined")throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org/docs/v2/)");super(e,t),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return Zh}static get DefaultType(){return ep}static get NAME(){return Ph}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){if(this._isEnabled){if(this._isShown()){this._leave();return}this._enter()}}dispose(){clearTimeout(this._timeout),x.off(this._element.closest(ta),na,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if(this._element.style.display==="none")throw new Error("Please use show on visible elements");if(!(this._isWithContent()&&this._isEnabled))return;let e=x.trigger(this._element,this.constructor.eventName(Wh)),i=(fa(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(e.defaultPrevented||!i)return;this._disposePopper();let r=this._getTipElement();this._element.setAttribute("aria-describedby",r.getAttribute("id"));let{container:o}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(o.append(r),x.trigger(this._element,this.constructor.eventName(Uh))),this._popper=this._createPopper(r),r.classList.add(zi),"ontouchstart"in document.documentElement)for(let a of[].concat(...document.body.children))x.on(a,"mouseover",Yi);let s=()=>{x.trigger(this._element,this.constructor.eventName(qh)),this._isHovered===!1&&this._leave(),this._isHovered=!1};this._queueCallback(s,this.tip,this._isAnimated())}hide(){if(!this._isShown()||x.trigger(this._element,this.constructor.eventName(zh)).defaultPrevented)return;if(this._getTipElement().classList.remove(zi),"ontouchstart"in document.documentElement)for(let r of[].concat(...document.body.children))x.off(r,"mouseover",Yi);this._activeTrigger[vo]=!1,this._activeTrigger[go]=!1,this._activeTrigger[Qn]=!1,this._isHovered=null;let i=()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),x.trigger(this._element,this.constructor.eventName(jh)))};this._queueCallback(i,this.tip,this._isAnimated())}update(){this._popper&&this._popper.update()}_isWithContent(){return!!this._getTitle()}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(e){let t=this._getTemplateFactory(e).toHtml();if(!t)return null;t.classList.remove(mo,zi),t.classList.add(`bs-${this.constructor.NAME}-auto`);let i=Vu(this.constructor.NAME).toString();return t.setAttribute("id",i),this._isAnimated()&&t.classList.add(mo),t}setContent(e){this._newContent=e,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(e){return this._templateFactory?this._templateFactory.changeContent(e):this._templateFactory=new So(se(D({},this._config),{content:e,extraClass:this._resolvePossibleFunction(this._config.customClass)})),this._templateFactory}_getContentForTemplate(){return{[Bh]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(e){return this.constructor.getOrCreateInstance(e.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(mo)}_isShown(){return this.tip&&this.tip.classList.contains(zi)}_createPopper(e){let t=Pe(this._config.placement,[this,e,this._element]),i=Jh[t.toUpperCase()];return Kn(this._element,e,this._getPopperConfig(i))}_getOffset(){let{offset:e}=this._config;return typeof e=="string"?e.split(",").map(t=>Number.parseInt(t,10)):typeof e=="function"?t=>e(t,this._element):e}_resolvePossibleFunction(e){return Pe(e,[this._element,this._element])}_getPopperConfig(e){let t={placement:e,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:i=>{this._getTipElement().setAttribute("data-popper-placement",i.state.placement)}}]};return D(D({},t),Pe(this._config.popperConfig,[void 0,t]))}_setListeners(){let e=this._config.trigger.split(" ");for(let t of e)if(t==="click")x.on(this._element,this.constructor.eventName(Yh),this._config.selector,i=>{let r=this._initializeOnDelegatedTarget(i);r._activeTrigger[vo]=!(r._isShown()&&r._activeTrigger[vo]),r.toggle()});else if(t!==Vh){let i=t===Qn?this.constructor.eventName(Xh):this.constructor.eventName(Gh),r=t===Qn?this.constructor.eventName(Qh):this.constructor.eventName(Kh);x.on(this._element,i,this._config.selector,o=>{let s=this._initializeOnDelegatedTarget(o);s._activeTrigger[o.type==="focusin"?go:Qn]=!0,s._enter()}),x.on(this._element,r,this._config.selector,o=>{let s=this._initializeOnDelegatedTarget(o);s._activeTrigger[o.type==="focusout"?go:Qn]=s._element.contains(o.relatedTarget),s._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},x.on(this._element.closest(ta),na,this._hideModalHandler)}_fixTitle(){let e=this._element.getAttribute("title");e&&(!this._element.getAttribute("aria-label")&&!this._element.textContent.trim()&&this._element.setAttribute("aria-label",e),this._element.setAttribute("data-bs-original-title",e),this._element.removeAttribute("title"))}_enter(){if(this._isShown()||this._isHovered){this._isHovered=!0;return}this._isHovered=!0,this._setTimeout(()=>{this._isHovered&&this.show()},this._config.delay.show)}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout(()=>{this._isHovered||this.hide()},this._config.delay.hide))}_setTimeout(e,t){clearTimeout(this._timeout),this._timeout=setTimeout(e,t)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(e){let t=ft.getDataAttributes(this._element);for(let i of Object.keys(t))Fh.has(i)&&delete t[i];return e=D(D({},t),typeof e=="object"&&e?e:{}),e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e.container=e.container===!1?document.body:xt(e.container),typeof e.delay=="number"&&(e.delay={show:e.delay,hide:e.delay}),typeof e.title=="number"&&(e.title=e.title.toString()),typeof e.content=="number"&&(e.content=e.content.toString()),e}_getDelegateConfig(){let e={};for(let[t,i]of Object.entries(this._config))this.constructor.Default[t]!==i&&(e[t]=i);return e.selector=!1,e.trigger="manual",e}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e]()}})}};Je(ht);var tp="popover",np=".popover-header",ip=".popover-body",rp=se(D({},ht.Default),{content:"",offset:[0,8],placement:"right",template:'',trigger:"click"}),op=se(D({},ht.DefaultType),{content:"(null|string|element|function)"}),Tn=class n extends ht{static get Default(){return rp}static get DefaultType(){return op}static get NAME(){return tp}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[np]:this._getTitle(),[ip]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e]()}})}};Je(Tn);var sp="scrollspy",ap="bs.scrollspy",Lo=`.${ap}`,lp=".data-api",cp=`activate${Lo}`,ia=`click${Lo}`,up=`load${Lo}${lp}`,dp="dropdown-item",bn="active",fp='[data-bs-spy="scroll"]',yo="[href]",hp=".nav, .list-group",ra=".nav-link",pp=".nav-item",mp=".list-group-item",gp=`${ra}, ${pp} > ${ra}, ${mp}`,vp=".dropdown",yp=".dropdown-toggle",Ep={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},bp={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"},er=class n extends qe{constructor(e,t){super(e,t),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement=getComputedStyle(this._element).overflowY==="visible"?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Ep}static get DefaultType(){return bp}static get NAME(){return sp}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(let e of this._observableSections.values())this._observer.observe(e)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(e){return e.target=xt(e.target)||document.body,e.rootMargin=e.offset?`${e.offset}px 0px -30%`:e.rootMargin,typeof e.threshold=="string"&&(e.threshold=e.threshold.split(",").map(t=>Number.parseFloat(t))),e}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(x.off(this._config.target,ia),x.on(this._config.target,ia,yo,e=>{let t=this._observableSections.get(e.target.hash);if(t){e.preventDefault();let i=this._rootElement||window,r=t.offsetTop-this._element.offsetTop;if(i.scrollTo){i.scrollTo({top:r,behavior:"smooth"});return}i.scrollTop=r}}))}_getNewObserver(){let e={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver(t=>this._observerCallback(t),e)}_observerCallback(e){let t=s=>this._targetLinks.get(`#${s.target.id}`),i=s=>{this._previousScrollData.visibleEntryTop=s.target.offsetTop,this._process(t(s))},r=(this._rootElement||document.documentElement).scrollTop,o=r>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=r;for(let s of e){if(!s.isIntersecting){this._activeTarget=null,this._clearActiveClass(t(s));continue}let a=s.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(o&&a){if(i(s),!r)return;continue}!o&&!a&&i(s)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;let e=z.find(yo,this._config.target);for(let t of e){if(!t.hash||Tt(t))continue;let i=z.findOne(decodeURI(t.hash),this._element);Cn(i)&&(this._targetLinks.set(decodeURI(t.hash),t),this._observableSections.set(t.hash,i))}}_process(e){this._activeTarget!==e&&(this._clearActiveClass(this._config.target),this._activeTarget=e,e.classList.add(bn),this._activateParents(e),x.trigger(this._element,cp,{relatedTarget:e}))}_activateParents(e){if(e.classList.contains(dp)){z.findOne(yp,e.closest(vp)).classList.add(bn);return}for(let t of z.parents(e,hp))for(let i of z.prev(t,gp))i.classList.add(bn)}_clearActiveClass(e){e.classList.remove(bn);let t=z.find(`${yo}.${bn}`,e);for(let i of t)i.classList.remove(bn)}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e]()}})}};x.on(window,up,()=>{for(let n of z.find(fp))er.getOrCreateInstance(n)});Je(er);var _p="tab",wp="bs.tab",Zt=`.${wp}`,xp=`hide${Zt}`,Tp=`hidden${Zt}`,Cp=`show${Zt}`,Sp=`shown${Zt}`,Ap=`click${Zt}`,Dp=`keydown${Zt}`,Op=`load${Zt}`,Lp="ArrowLeft",oa="ArrowRight",Mp="ArrowUp",sa="ArrowDown",Eo="Home",aa="End",Kt="active",la="fade",bo="show",Np="dropdown",Ia=".dropdown-toggle",kp=".dropdown-menu",_o=`:not(${Ia})`,Hp='.list-group, .nav, [role="tablist"]',Ip=".nav-item, .list-group-item",Rp=`.nav-link${_o}, .list-group-item${_o}, [role="tab"]${_o}`,Ra='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',wo=`${Rp}, ${Ra}`,Pp=`.${Kt}[data-bs-toggle="tab"], .${Kt}[data-bs-toggle="pill"], .${Kt}[data-bs-toggle="list"]`,Qt=class n extends qe{constructor(e){super(e),this._parent=this._element.closest(Hp),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),x.on(this._element,Dp,t=>this._keydown(t)))}static get NAME(){return _p}show(){let e=this._element;if(this._elemIsActive(e))return;let t=this._getActiveElem(),i=t?x.trigger(t,xp,{relatedTarget:e}):null;x.trigger(e,Cp,{relatedTarget:t}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(t,e),this._activate(e,t))}_activate(e,t){if(!e)return;e.classList.add(Kt),this._activate(z.getElementFromSelector(e));let i=()=>{if(e.getAttribute("role")!=="tab"){e.classList.add(bo);return}e.removeAttribute("tabindex"),e.setAttribute("aria-selected",!0),this._toggleDropDown(e,!0),x.trigger(e,Sp,{relatedTarget:t})};this._queueCallback(i,e,e.classList.contains(la))}_deactivate(e,t){if(!e)return;e.classList.remove(Kt),e.blur(),this._deactivate(z.getElementFromSelector(e));let i=()=>{if(e.getAttribute("role")!=="tab"){e.classList.remove(bo);return}e.setAttribute("aria-selected",!1),e.setAttribute("tabindex","-1"),this._toggleDropDown(e,!1),x.trigger(e,Tp,{relatedTarget:t})};this._queueCallback(i,e,e.classList.contains(la))}_keydown(e){if(![Lp,oa,Mp,sa,Eo,aa].includes(e.key))return;e.stopPropagation(),e.preventDefault();let t=this._getChildren().filter(r=>!Tt(r)),i;if([Eo,aa].includes(e.key))i=t[e.key===Eo?0:t.length-1];else{let r=[oa,sa].includes(e.key);i=Ao(t,e.target,r,!0)}i&&(i.focus({preventScroll:!0}),n.getOrCreateInstance(i).show())}_getChildren(){return z.find(wo,this._parent)}_getActiveElem(){return this._getChildren().find(e=>this._elemIsActive(e))||null}_setInitialAttributes(e,t){this._setAttributeIfNotExists(e,"role","tablist");for(let i of t)this._setInitialAttributesOnChild(i)}_setInitialAttributesOnChild(e){e=this._getInnerElement(e);let t=this._elemIsActive(e),i=this._getOuterElement(e);e.setAttribute("aria-selected",t),i!==e&&this._setAttributeIfNotExists(i,"role","presentation"),t||e.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(e,"role","tab"),this._setInitialAttributesOnTargetPanel(e)}_setInitialAttributesOnTargetPanel(e){let t=z.getElementFromSelector(e);t&&(this._setAttributeIfNotExists(t,"role","tabpanel"),e.id&&this._setAttributeIfNotExists(t,"aria-labelledby",`${e.id}`))}_toggleDropDown(e,t){let i=this._getOuterElement(e);if(!i.classList.contains(Np))return;let r=(o,s)=>{let a=z.findOne(o,i);a&&a.classList.toggle(s,t)};r(Ia,Kt),r(kp,bo),i.setAttribute("aria-expanded",t)}_setAttributeIfNotExists(e,t,i){e.hasAttribute(t)||e.setAttribute(t,i)}_elemIsActive(e){return e.classList.contains(Kt)}_getInnerElement(e){return e.matches(wo)?e:z.findOne(wo,e)}_getOuterElement(e){return e.closest(Ip)||e}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e]()}})}};x.on(document,Ap,Ra,function(n){["A","AREA"].includes(this.tagName)&&n.preventDefault(),!Tt(this)&&Qt.getOrCreateInstance(this).show()});x.on(window,Op,()=>{for(let n of z.find(Pp))Qt.getOrCreateInstance(n)});Je(Qt);var Fp="toast",$p="bs.toast",Ot=`.${$p}`,Bp=`mouseover${Ot}`,Vp=`mouseout${Ot}`,zp=`focusin${Ot}`,jp=`focusout${Ot}`,Wp=`hide${Ot}`,qp=`hidden${Ot}`,Up=`show${Ot}`,Yp=`shown${Ot}`,Gp="fade",ca="hide",ji="show",Wi="showing",Kp={animation:"boolean",autohide:"boolean",delay:"number"},Xp={animation:!0,autohide:!0,delay:5e3},pt=class n extends qe{constructor(e,t){super(e,t),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return Xp}static get DefaultType(){return Kp}static get NAME(){return Fp}show(){if(x.trigger(this._element,Up).defaultPrevented)return;this._clearTimeout(),this._config.animation&&this._element.classList.add(Gp);let t=()=>{this._element.classList.remove(Wi),x.trigger(this._element,Yp),this._maybeScheduleHide()};this._element.classList.remove(ca),ei(this._element),this._element.classList.add(ji,Wi),this._queueCallback(t,this._element,this._config.animation)}hide(){if(!this.isShown()||x.trigger(this._element,Wp).defaultPrevented)return;let t=()=>{this._element.classList.add(ca),this._element.classList.remove(Wi,ji),x.trigger(this._element,qp)};this._element.classList.add(Wi),this._queueCallback(t,this._element,this._config.animation)}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(ji),super.dispose()}isShown(){return this._element.classList.contains(ji)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(e,t){switch(e.type){case"mouseover":case"mouseout":{this._hasMouseInteraction=t;break}case"focusin":case"focusout":{this._hasKeyboardInteraction=t;break}}if(t){this._clearTimeout();return}let i=e.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){x.on(this._element,Bp,e=>this._onInteraction(e,!0)),x.on(this._element,Vp,e=>this._onInteraction(e,!1)),x.on(this._element,zp,e=>this._onInteraction(e,!0)),x.on(this._element,jp,e=>this._onInteraction(e,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e](this)}})}};tr(pt);Je(pt);var Qp=(function(){"use strict";let htmx={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(n,e){return getInputValues(n,e||"post").values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,allowScriptTags:!0,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:!1,getCacheBusterParam:!1,globalViewTransitions:!1,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:!0,ignoreTitle:!1,scrollIntoViewOnBoost:!0,triggerSpecsCache:null,disableInheritance:!1,responseHandling:[{code:"204",swap:!1},{code:"[23]..",swap:!0},{code:"[45]..",swap:!1,error:!0}],allowNestedOobSwaps:!0,historyRestoreAsHxRequest:!0,reportValidityOfForms:!1},parseInterval:null,location,_:null,version:"2.0.10"};htmx.onLoad=onLoadHelper,htmx.process=processNode,htmx.on=addEventListenerImpl,htmx.off=removeEventListenerImpl,htmx.trigger=triggerEvent,htmx.ajax=ajaxHelper,htmx.find=find,htmx.findAll=findAll,htmx.closest=closest,htmx.remove=removeElement,htmx.addClass=addClassToElement,htmx.removeClass=removeClassFromElement,htmx.toggleClass=toggleClassOnElement,htmx.takeClass=takeClassForElement,htmx.swap=swap,htmx.defineExtension=defineExtension,htmx.removeExtension=removeExtension,htmx.logAll=logAll,htmx.logNone=logNone,htmx.parseInterval=parseInterval,htmx._=internalEval;let internalAPI={addTriggerHandler,bodyContains,canAccessLocalStorage,findThisElement,filterValues,swap,hasAttribute,getAttributeValue,getClosestAttributeValue,getClosestMatch,getExpressionVars,getHeaders,getInputValues,getInternalData,getSwapSpecification,getTriggerSpecs,getTarget,makeFragment,mergeObjects,makeSettleInfo,oobSwap,querySelectorExt,settleImmediately,shouldCancel,triggerEvent,triggerErrorEvent,withExtensions},VERBS=["get","post","put","delete","patch"],VERB_SELECTOR=VERBS.map(function(n){return"[hx-"+n+"], [data-hx-"+n+"]"}).join(", ");function parseInterval(n){if(n==null)return;let e=NaN;return n.slice(-2)=="ms"?e=parseFloat(n.slice(0,-2)):n.slice(-1)=="s"?e=parseFloat(n.slice(0,-1))*1e3:n.slice(-1)=="m"?e=parseFloat(n.slice(0,-1))*1e3*60:e=parseFloat(n),isNaN(e)?void 0:e}function getRawAttribute(n,e){return n instanceof Element&&n.getAttribute(e)}function hasAttribute(n,e){return!!n.hasAttribute&&(n.hasAttribute(e)||n.hasAttribute("data-"+e))}function getAttributeValue(n,e){return getRawAttribute(n,e)||getRawAttribute(n,"data-"+e)}function parentElt(n){let e=n.parentElement;return!e&&n.parentNode instanceof ShadowRoot?n.parentNode:e}function getDocument(){return document}function getRootNode(n,e){return n.getRootNode?n.getRootNode({composed:e}):getDocument()}function getClosestMatch(n,e){for(;n&&!e(n);)n=parentElt(n);return n||null}function getAttributeValueWithDisinheritance(n,e,t){let i=getAttributeValue(e,t),r=getAttributeValue(e,"hx-disinherit");var o=getAttributeValue(e,"hx-inherit");if(n!==e){if(htmx.config.disableInheritance)return o&&(o==="*"||o.split(" ").indexOf(t)>=0)?i:null;if(r&&(r==="*"||r.split(" ").indexOf(t)>=0))return"unset"}return i}function getClosestAttributeValue(n,e){let t=null;if(getClosestMatch(n,function(i){return!!(t=getAttributeValueWithDisinheritance(n,asElement(i),e))}),t!=="unset")return t}function matches(n,e){return n instanceof Element&&n.matches(e)}function getStartTag(n){let t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i.exec(n);return t?t[1].toLowerCase():""}function parseHTML(n){return"parseHTMLUnsafe"in Document?Document.parseHTMLUnsafe(n):new DOMParser().parseFromString(n,"text/html")}function takeChildrenFor(n,e){for(;e.childNodes.length>0;)n.append(e.childNodes[0])}function duplicateScript(n){let e=getDocument().createElement("script");return forEach(n.attributes,function(t){e.setAttribute(t.name,t.value)}),e.textContent=n.textContent,e.async=!1,htmx.config.inlineScriptNonce&&(e.nonce=htmx.config.inlineScriptNonce),e}function isJavaScriptScriptNode(n){return n.matches("script")&&(n.type==="text/javascript"||n.type==="module"||n.type==="")}function normalizeScriptTags(n){Array.from(n.querySelectorAll("script")).forEach(e=>{if(isJavaScriptScriptNode(e)){let t=duplicateScript(e),i=e.parentNode;try{i.insertBefore(t,e)}catch(r){logError(r)}finally{e.remove()}}})}function makeFragment(n){let e=n.replace(/]*)?>[\s\S]*?<\/head>/i,""),t=getStartTag(e),i;if(t==="html"){i=new DocumentFragment;let o=parseHTML(n);takeChildrenFor(i,o.body),i.title=o.title}else if(t==="body"){i=new DocumentFragment;let o=parseHTML(e);takeChildrenFor(i,o.body),i.title=o.title}else{let o=parseHTML('");i=o.querySelector("template").content,i.title=o.title;var r=i.querySelector("title");r&&r.parentNode===i&&(r.remove(),i.title=r.innerText)}return i&&(htmx.config.allowScriptTags?normalizeScriptTags(i):i.querySelectorAll("script").forEach(o=>o.remove())),i}function maybeCall(n){n&&n()}function isType(n,e){return Object.prototype.toString.call(n)==="[object "+e+"]"}function isFunction(n){return typeof n=="function"}function isRawObject(n){return isType(n,"Object")}function getInternalData(n){let e="htmx-internal-data",t=n[e];return t||(t=n[e]={}),t}function toArray(n){let e=[];if(n)for(let t=0;t=0}function bodyContains(n){return n.getRootNode({composed:!0})===document}function splitOnWhitespace(n){return n.trim().split(/\s+/)}function mergeObjects(n,e){for(let t in e)e.hasOwnProperty(t)&&(n[t]=e[t]);return n}function parseJSON(n){try{return JSON.parse(n)}catch(e){return logError(e),null}}function canAccessLocalStorage(){let n="htmx:sessionStorageTest";try{return sessionStorage.setItem(n,n),sessionStorage.removeItem(n),!0}catch(e){return!1}}function normalizePath(n){try{let e=new URL(n,window.location.href);n=e.pathname+e.search}catch(e){}return n!="/"&&(n=n.replace(/\/+$/,"")),n}function internalEval(str){return maybeEval(getDocument().body,function(){return eval(str)})}function onLoadHelper(n){return htmx.on("htmx:load",function(t){n(t.detail.elt)})}function logAll(){htmx.logger=function(n,e,t){console&&console.log(e,n,t)}}function logNone(){htmx.logger=null}function find(n,e){return typeof n!="string"?n.querySelector(e):find(getDocument(),n)}function findAll(n,e){return typeof n!="string"?n.querySelectorAll(e):findAll(getDocument(),n)}function getWindow(){return window}function removeElement(n,e){n=resolveTarget(n),e?getWindow().setTimeout(function(){removeElement(n),n=null},e):parentElt(n).removeChild(n)}function asElement(n){return n instanceof Element?n:null}function asHtmlElement(n){return n instanceof HTMLElement?n:null}function asString(n){return typeof n=="string"?n:null}function asParentNode(n){return n instanceof Element||n instanceof Document||n instanceof DocumentFragment?n:null}function addClassToElement(n,e,t){n=asElement(resolveTarget(n)),n&&(t?getWindow().setTimeout(function(){addClassToElement(n,e),n=null},t):n.classList&&n.classList.add(e))}function removeClassFromElement(n,e,t){let i=asElement(resolveTarget(n));i&&(t?getWindow().setTimeout(function(){removeClassFromElement(i,e),i=null},t):i.classList&&(i.classList.remove(e),i.classList.length===0&&i.removeAttribute("class")))}function toggleClassOnElement(n,e){n=resolveTarget(n),n.classList.toggle(e)}function takeClassForElement(n,e){n=resolveTarget(n),forEach(n.parentElement.children,function(t){removeClassFromElement(t,e)}),addClassToElement(asElement(n),e)}function closest(n,e){return n=asElement(resolveTarget(n)),n?n.closest(e):null}function startsWith(n,e){return n.substring(0,e.length)===e}function endsWith(n,e){return n.substring(n.length-e.length)===e}function normalizeSelector(n){let e=n.trim();return startsWith(e,"<")&&endsWith(e,"/>")?e.substring(1,e.length-2):e}function querySelectorAllExt(n,e,t){if(e.indexOf("global ")===0)return querySelectorAllExt(n,e.slice(7),!0);n=resolveTarget(n);let i=[];{let s=0,a=0;for(let l=0;l"&&s--}a0;){let s=normalizeSelector(i.shift()),a;s.indexOf("closest ")===0?a=closest(asElement(n),normalizeSelector(s.slice(8))):s.indexOf("find ")===0?a=find(asParentNode(n),normalizeSelector(s.slice(5))):s==="next"||s==="nextElementSibling"?a=asElement(n).nextElementSibling:s.indexOf("next ")===0?a=scanForwardQuery(n,normalizeSelector(s.slice(5)),!!t):s==="previous"||s==="previousElementSibling"?a=asElement(n).previousElementSibling:s.indexOf("previous ")===0?a=scanBackwardsQuery(n,normalizeSelector(s.slice(9)),!!t):s==="document"?a=document:s==="window"?a=window:s==="body"?a=document.body:s==="root"?a=getRootNode(n,!!t):s==="host"?a=n.getRootNode().host:o.push(s),a&&r.push(a)}if(o.length>0){let s=o.join(","),a=asParentNode(getRootNode(n,!!t));r.push(...toArray(a.querySelectorAll(s)))}return r}var scanForwardQuery=function(n,e,t){let i=asParentNode(getRootNode(n,t)).querySelectorAll(e);for(let r=0;r=0;r--){let o=i[r];if(o.compareDocumentPosition(n)===Node.DOCUMENT_POSITION_FOLLOWING)return o}};function querySelectorExt(n,e){return typeof n!="string"?querySelectorAllExt(n,e)[0]:querySelectorAllExt(getDocument().body,n)[0]}function resolveTarget(n,e){return typeof n=="string"?find(asParentNode(e)||document,n):n}function processEventArgs(n,e,t,i){return isFunction(e)?{target:getDocument().body,event:asString(n),listener:e,options:t}:{target:resolveTarget(n),event:asString(e),listener:t,options:i}}function addEventListenerImpl(n,e,t,i){return ready(function(){let o=processEventArgs(n,e,t,i);o.target.addEventListener(o.event,o.listener,o.options)}),isFunction(e)?e:t}function removeEventListenerImpl(n,e,t){return ready(function(){let i=processEventArgs(n,e,t);i.target.removeEventListener(i.event,i.listener)}),isFunction(e)?e:t}let DUMMY_ELT=getDocument().createElement("output");function findAttributeTargets(n,e){let t=getClosestAttributeValue(n,e);if(t){if(t==="this")return[findThisElement(n,e)];{let i=querySelectorAllExt(n,t);if(/(^|,)(\s*)inherit(\s*)($|,)/.test(t)){let o=asElement(getClosestMatch(n,function(s){return s!==n&&hasAttribute(asElement(s),e)}));o&&i.push(...findAttributeTargets(o,e))}return i.length===0?(logError('The selector "'+t+'" on '+e+" returned no matches!"),[DUMMY_ELT]):i}}}function findThisElement(n,e){return asElement(getClosestMatch(n,function(t){return getAttributeValue(asElement(t),e)!=null}))}function getTarget(n){let e=getClosestAttributeValue(n,"hx-target");return e?e==="this"?findThisElement(n,"hx-target"):querySelectorExt(n,e):getInternalData(n).boosted?getDocument().body:n}function shouldSettleAttribute(n){return htmx.config.attributesToSettle.includes(n)}function cloneAttributes(n,e){forEach(Array.from(n.attributes),function(t){!e.hasAttribute(t.name)&&shouldSettleAttribute(t.name)&&n.removeAttribute(t.name)}),forEach(e.attributes,function(t){shouldSettleAttribute(t.name)&&n.setAttribute(t.name,t.value)})}function isInlineSwap(n,e){let t=getExtensions(e);for(let i=0;i0?(o=n.substring(0,n.indexOf(":")),r=n.substring(n.indexOf(":")+1)):o=n),e.removeAttribute("hx-swap-oob"),e.removeAttribute("data-hx-swap-oob");let s=querySelectorAllExt(i,r,!1);return s.length?(forEach(s,function(a){let l,c=e.cloneNode(!0);l=getDocument().createDocumentFragment(),l.appendChild(c),isInlineSwap(o,a)||(l=asParentNode(c));let u={shouldSwap:!0,target:a,fragment:l};triggerEvent(a,"htmx:oobBeforeSwap",u)&&(a=u.target,u.shouldSwap&&(handlePreservedElements(l),swapWithStyle(o,a,a,l,t),restorePreservedElements()),forEach(t.elts,function(d){triggerEvent(d,"htmx:oobAfterSwap",u)}))}),e.parentNode.removeChild(e)):(e.parentNode.removeChild(e),triggerErrorEvent(getDocument().body,"htmx:oobErrorNoTarget",{content:e,target:r})),n}function restorePreservedElements(){let n=find("#--htmx-preserve-pantry--");if(n){for(let e of[...n.children]){let t=find("#"+e.id);t.parentNode.moveBefore(e,t),t.remove()}n.remove()}}function handlePreservedElements(n){forEach(findAll(n,"[hx-preserve], [data-hx-preserve]"),function(e){let t=getAttributeValue(e,"id"),i=getDocument().getElementById(t);if(i!=null)if(e.moveBefore){let r=find("#--htmx-preserve-pantry--");r==null&&(getDocument().body.insertAdjacentHTML("afterend","
"),r=find("#--htmx-preserve-pantry--")),r.moveBefore(i,null)}else e.parentNode.replaceChild(i,e)})}function handleAttributes(n,e,t){forEach(e.querySelectorAll("[id]"),function(i){let r=getRawAttribute(i,"id");if(r&&r.length>0){let o=asParentNode(n),s=o&&o.querySelector(CSS.escape(i.tagName)+"#"+CSS.escape(r));if(s&&s!==o){let a=i.cloneNode();cloneAttributes(i,s),t.tasks.push(function(){cloneAttributes(i,a)})}}})}function makeAjaxLoadTask(n){return function(){removeClassFromElement(n,htmx.config.addedClass),processNode(asElement(n)),processFocus(asParentNode(n)),triggerEvent(n,"htmx:load")}}function processFocus(n){let e="[autofocus]",t=asHtmlElement(matches(n,e)?n:n.querySelector(e));t!=null&&t.focus()}function insertNodesBefore(n,e,t,i){for(handleAttributes(n,t,i);t.childNodes.length>0;){let r=t.firstChild;addClassToElement(asElement(r),htmx.config.addedClass),n.insertBefore(r,e),r.nodeType!==Node.TEXT_NODE&&r.nodeType!==Node.COMMENT_NODE&&i.tasks.push(makeAjaxLoadTask(r))}}function stringHash(n,e){let t=0;for(;t0}function swap(n,e,t,i){i||(i={});let r=null,o=null,s=function(){maybeCall(i.beforeSwapCallback),n=resolveTarget(n);let c=i.contextElement?getRootNode(i.contextElement,!1):getDocument(),u=document.activeElement,d={};d={elt:u,start:u?u.selectionStart:null,end:u?u.selectionEnd:null};let p=makeSettleInfo(n);if(t.swapStyle==="textContent")n.textContent=e;else{let m=makeFragment(e);if(p.title=i.title||m.title,i.historyRequest&&(m=m.querySelector("[hx-history-elt],[data-hx-history-elt]")||m),i.selectOOB){let g=i.selectOOB.split(",");for(let _=0;_0?getWindow().setTimeout(y,t.settleDelay):y()},a=htmx.config.globalViewTransitions;t.hasOwnProperty("transition")&&(a=t.transition);let l=i.contextElement||getDocument();if(a&&triggerEvent(l,"htmx:beforeTransition",i.eventInfo)&&typeof Promise!="undefined"&&document.startViewTransition){let c=new Promise(function(d,p){r=d,o=p}),u=s;s=function(){document.startViewTransition(function(){return u(),c})}}try{t!=null&&t.swapDelay&&t.swapDelay>0?getWindow().setTimeout(s,t.swapDelay):s()}catch(c){throw triggerErrorEvent(l,"htmx:swapError",i.eventInfo),maybeCall(o),c}}function handleTriggerHeader(n,e,t){let i=n.getResponseHeader(e);if(i.indexOf("{")===0){let r=parseJSON(i);for(let o in r)if(r.hasOwnProperty(o)){let s=r[o];isRawObject(s)?t=s.target!==void 0?s.target:t:s={value:s},triggerEvent(t,o,s)}}else{let r=i.split(",");for(let o=0;o0;){let s=e[0];if(s==="]"){if(i--,i===0){o===null&&(r=r+"true"),e.shift(),r+=")})";try{let a=maybeEval(n,function(){return Function(r)()},function(){return!0});return a.source=r,a}catch(a){return triggerErrorEvent(getDocument().body,"htmx:syntax:error",{error:a,source:r}),null}}}else s==="["&&i++;isPossibleRelativeReference(s,o,t)?r+="(("+t+"."+s+") ? ("+t+"."+s+") : (window."+s+"))":r=r+s,o=e.shift()}}}function consumeUntil(n,e){let t="";for(;n.length>0&&!e.test(n[0]);)t+=n.shift();return t}function consumeCSSSelector(n){let e;return n.length>0&&COMBINED_SELECTOR_START.test(n[0])?(n.shift(),e=consumeUntil(n,COMBINED_SELECTOR_END).trim(),n.shift()):e=consumeUntil(n,WHITESPACE_OR_COMMA),e}let INPUT_SELECTOR="input, textarea, select";function parseAndCacheTrigger(n,e,t){let i=[],r=tokenizeString(e);do{consumeUntil(r,NOT_WHITESPACE);let a=r.length,l=consumeUntil(r,/[,\[\s]/);if(l!=="")if(l==="every"){let c={trigger:"every"};consumeUntil(r,NOT_WHITESPACE),c.pollInterval=parseInterval(consumeUntil(r,/[,\[\s]/)),consumeUntil(r,NOT_WHITESPACE);var o=maybeGenerateConditional(n,r,"event");o&&(c.eventFilter=o),i.push(c)}else{let c={trigger:l};var o=maybeGenerateConditional(n,r,"event");for(o&&(c.eventFilter=o),consumeUntil(r,NOT_WHITESPACE);r.length>0&&r[0]!==",";){let d=r.shift();if(d==="changed")c.changed=!0;else if(d==="once")c.once=!0;else if(d==="consume")c.consume=!0;else if(d==="delay"&&r[0]===":")r.shift(),c.delay=parseInterval(consumeUntil(r,WHITESPACE_OR_COMMA));else if(d==="from"&&r[0]===":"){if(r.shift(),COMBINED_SELECTOR_START.test(r[0]))var s=consumeCSSSelector(r);else{var s=consumeUntil(r,WHITESPACE_OR_COMMA);if(s==="closest"||s==="find"||s==="next"||s==="previous"){r.shift();let y=consumeCSSSelector(r);y.length>0&&(s+=" "+y)}}c.from=s}else d==="target"&&r[0]===":"?(r.shift(),c.target=consumeCSSSelector(r)):d==="throttle"&&r[0]===":"?(r.shift(),c.throttle=parseInterval(consumeUntil(r,WHITESPACE_OR_COMMA))):d==="queue"&&r[0]===":"?(r.shift(),c.queue=consumeUntil(r,WHITESPACE_OR_COMMA)):d==="root"&&r[0]===":"?(r.shift(),c[d]=consumeCSSSelector(r)):d==="threshold"&&r[0]===":"?(r.shift(),c[d]=consumeUntil(r,WHITESPACE_OR_COMMA)):triggerErrorEvent(n,"htmx:syntax:error",{token:r.shift()});consumeUntil(r,NOT_WHITESPACE)}i.push(c)}r.length===a&&triggerErrorEvent(n,"htmx:syntax:error",{token:r.shift()}),consumeUntil(r,NOT_WHITESPACE)}while(r[0]===","&&r.shift());return t&&(t[e]=i),i}function getTriggerSpecs(n){let e=getAttributeValue(n,"hx-trigger"),t=[];if(e){let i=htmx.config.triggerSpecsCache;t=i&&i[e]||parseAndCacheTrigger(n,e,i)}return t.length>0?t:matches(n,"form")?[{trigger:"submit"}]:matches(n,'input[type="button"], input[type="submit"]')?[{trigger:"click"}]:matches(n,INPUT_SELECTOR)?[{trigger:"change"}]:[{trigger:"click"}]}function cancelPolling(n){getInternalData(n).cancelled=!0}function processPolling(n,e,t){let i=getInternalData(n);i.timeout=getWindow().setTimeout(function(){bodyContains(n)&&i.cancelled!==!0&&(maybeFilterEvent(t,n,makeEvent("hx:poll:trigger",{triggerSpec:t,target:n}))||e(n),processPolling(n,e,t))},t.pollInterval)}function isLocalLink(n){return location.hostname===n.hostname&&getRawAttribute(n,"href")&&getRawAttribute(n,"href").indexOf("#")!==0}function eltIsDisabled(n){return closest(n,htmx.config.disableSelector)}function boostElement(n,e,t){if(n instanceof HTMLAnchorElement&&isLocalLink(n)&&(n.target===""||n.target==="_self")||n.tagName==="FORM"&&String(getRawAttribute(n,"method")).toLowerCase()!=="dialog"){e.boosted=!0;let i,r;if(n.tagName==="A")i="get",r=getRawAttribute(n,"href");else{let o=getRawAttribute(n,"method");i=o?o.toLowerCase():"get",r=getRawAttribute(n,"action"),(r==null||r==="")&&(r=location.href),i==="get"&&r.includes("?")&&(r=r.replace(/\?[^#]+/,""))}t.forEach(function(o){addEventListener(n,function(s,a){let l=asElement(s);if(eltIsDisabled(l)){cleanUpElement(l);return}issueAjaxRequest(i,r,l,a)},e,o,!0)})}}function shouldCancel(n,e){if(n.type==="submit"&&e.tagName==="FORM")return!0;if(n.type==="click"){let t=e.closest('input[type="submit"], button');if(t&&t.form&&t.type==="submit")return!0;let i=e.closest("a"),r=/^#.+/;if(i&&i.href&&!r.test(i.getAttribute("href")))return!0}return!1}function ignoreBoostedAnchorCtrlClick(n,e){return getInternalData(n).boosted&&n instanceof HTMLAnchorElement&&e.type==="click"&&(e.ctrlKey||e.metaKey)}function maybeFilterEvent(n,e,t){let i=n.eventFilter;if(i)try{return i.call(e,t)!==!0}catch(r){let o=i.source;return triggerErrorEvent(getDocument().body,"htmx:eventFilter:error",{error:r,source:o}),!0}return!1}function addEventListener(n,e,t,i,r){let o=getInternalData(n),s;i.from?s=querySelectorAllExt(n,i.from):s=[n],i.changed&&("lastValue"in o||(o.lastValue=new WeakMap),s.forEach(function(a){o.lastValue.has(i)||o.lastValue.set(i,new WeakMap),o.lastValue.get(i).set(a,a.value)})),forEach(s,function(a){let l=function(c){if(!bodyContains(n)){a.removeEventListener(i.trigger,l);return}if(ignoreBoostedAnchorCtrlClick(n,c)||((r||shouldCancel(c,a))&&c.preventDefault(),maybeFilterEvent(i,n,c)))return;let u=getInternalData(c);if(u.triggerSpec=i,u.handledFor==null&&(u.handledFor=[]),u.handledFor.indexOf(n)<0){if(u.handledFor.push(n),i.consume&&c.stopPropagation(),i.target&&c.target&&!matches(asElement(c.target),i.target))return;if(i.once){if(o.triggeredOnce)return;o.triggeredOnce=!0}if(i.changed){let d=c.target,p=d.value,y=o.lastValue.get(i);if(y.has(d)&&y.get(d)===p)return;y.set(d,p)}if(o.delayed&&clearTimeout(o.delayed),o.throttle)return;i.throttle>0?o.throttle||(triggerEvent(n,"htmx:trigger"),e(n,c),o.throttle=getWindow().setTimeout(function(){o.throttle=null},i.throttle)):i.delay>0?o.delayed=getWindow().setTimeout(function(){triggerEvent(n,"htmx:trigger"),e(n,c)},i.delay):(triggerEvent(n,"htmx:trigger"),e(n,c))}};t.listenerInfos==null&&(t.listenerInfos=[]),t.listenerInfos.push({trigger:i.trigger,listener:l,on:a}),a.addEventListener(i.trigger,l)})}let windowIsScrolling=!1,scrollHandler=null;function initScrollHandler(){scrollHandler||(scrollHandler=function(){windowIsScrolling=!0},window.addEventListener("scroll",scrollHandler),window.addEventListener("resize",scrollHandler),setInterval(function(){windowIsScrolling&&(windowIsScrolling=!1,forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(n){maybeReveal(n)}))},200))}function maybeReveal(n){!hasAttribute(n,"data-hx-revealed")&&isScrolledIntoView(n)&&(n.setAttribute("data-hx-revealed","true"),getInternalData(n).initHash?triggerEvent(n,"revealed"):n.addEventListener("htmx:afterProcessNode",function(){triggerEvent(n,"revealed")},{once:!0}))}function loadImmediately(n,e,t,i){let r=function(){t.loaded||(t.loaded=!0,triggerEvent(n,"htmx:trigger"),e(n))};i>0?getWindow().setTimeout(r,i):r()}function processVerbs(n,e,t){let i=!1;return forEach(VERBS,function(r){if(hasAttribute(n,"hx-"+r)){let o=getAttributeValue(n,"hx-"+r);i=!0,e.path=o,e.verb=r,t.forEach(function(s){addTriggerHandler(n,s,e,function(a,l){let c=asElement(a);if(eltIsDisabled(c)){cleanUpElement(c);return}issueAjaxRequest(r,o,c,l)})})}}),i}function addTriggerHandler(n,e,t,i){if(e.trigger==="revealed")initScrollHandler(),addEventListener(n,i,t,e),maybeReveal(asElement(n));else if(e.trigger==="intersect"){let r={};e.root&&(r.root=querySelectorExt(n,e.root)),e.threshold&&(r.threshold=parseFloat(e.threshold)),new IntersectionObserver(function(s){for(let a=0;a0?(t.polling=!0,processPolling(asElement(n),i,e)):addEventListener(n,i,t,e)}function shouldProcessHxOn(n){let e=asElement(n);if(!e)return!1;let t=e.attributes;for(let i=0;i", "+o).join(""))}else return[]}function maybeSetLastButtonClicked(n){let e=getTargetButton(n.target),t=getRelatedFormData(n);t&&(t.lastButtonClicked=e)}function maybeUnsetLastButtonClicked(n){let e=getRelatedFormData(n);e&&(e.lastButtonClicked=null)}function getTargetButton(n){return closest(asElement(n),"button, input[type='submit']")}function getRelatedForm(n){return n.form||closest(n,"form")}function getRelatedFormData(n){let e=getTargetButton(n.target);if(!e)return;let t=getRelatedForm(e);if(t)return getInternalData(t)}function initButtonTracking(n){n.addEventListener("click",maybeSetLastButtonClicked),n.addEventListener("focusin",maybeSetLastButtonClicked),n.addEventListener("focusout",maybeUnsetLastButtonClicked)}function addHxOnEventHandler(n,e,t){let i=getInternalData(n);Array.isArray(i.onHandlers)||(i.onHandlers=[]);let r,o=function(s){maybeEval(n,function(){eltIsDisabled(n)||(r||(r=new Function("event",t)),r.call(n,s))})};n.addEventListener(e,o),i.onHandlers.push({event:e,listener:o})}function processHxOnWildcard(n){deInitOnHandlers(n);for(let e=0;ehtmx.config.historyCacheSize;)o.shift();for(;o.length>0;)try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(o));break}catch(a){triggerErrorEvent(getDocument().body,"htmx:historyCacheError",{cause:a,cache:o}),o.shift()}}function getCachedHistory(n){if(!canAccessLocalStorage())return null;n=normalizePath(n);let e=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let t=0;t=200&&this.status<400?(i.response=this.response,triggerEvent(getDocument().body,"htmx:historyCacheMissLoad",i),swap(i.historyElt,i.response,t,{contextElement:i.historyElt,historyRequest:!0}),setCurrentPathForHistory(i.path),triggerEvent(getDocument().body,"htmx:historyRestore",{path:n,cacheMiss:!0,serverResponse:i.response})):triggerErrorEvent(getDocument().body,"htmx:historyCacheMissLoadError",i)},triggerEvent(getDocument().body,"htmx:historyCacheMiss",i)&&e.send()}function restoreHistory(n){saveCurrentPageToHistory(),n=n||location.pathname+location.search;let e=getCachedHistory(n);if(e){let t={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:e.scroll},i={path:n,item:e,historyElt:getHistoryElement(),swapSpec:t};triggerEvent(getDocument().body,"htmx:historyCacheHit",i)&&(swap(i.historyElt,e.content,t,{contextElement:i.historyElt,title:e.title}),setCurrentPathForHistory(i.path),triggerEvent(getDocument().body,"htmx:historyRestore",i))}else htmx.config.refreshOnHistoryMiss?htmx.location.reload(!0):loadHistoryFromServer(n)}function addRequestIndicatorClasses(n){let e=findAttributeTargets(n,"hx-indicator");return e==null&&(e=[n]),forEach(e,function(t){let i=getInternalData(t);i.requestCount=(i.requestCount||0)+1,addClassToElement(t,htmx.config.requestClass)}),e}function disableElements(n){let e=findAttributeTargets(n,"hx-disabled-elt");return e==null&&(e=[]),forEach(e,function(t){let i=getInternalData(t);i.requestCount=(i.requestCount||0)+1,t.hasAttribute("disabled")||(t.setAttribute("disabled",""),t.setAttribute("data-disabled-by-htmx",""))}),e}function removeRequestIndicators(n,e){forEach(n.concat(e),function(t){let i=getInternalData(t);i.requestCount=(i.requestCount||1)-1}),forEach(n,function(t){getInternalData(t).requestCount===0&&removeClassFromElement(t,htmx.config.requestClass)}),forEach(e,function(t){getInternalData(t).requestCount===0&&t.hasAttribute("data-disabled-by-htmx")&&(t.removeAttribute("disabled"),t.removeAttribute("data-disabled-by-htmx"))})}function haveSeenNode(n,e){for(let t=0;te.indexOf(r)<0):i=i.filter(r=>r!==e),t.delete(n),forEach(i,r=>t.append(n,r))}}function getValueFromInput(n){return n instanceof HTMLSelectElement&&n.multiple?toArray(n.querySelectorAll("option:checked")).map(function(e){return e.value}):n instanceof HTMLInputElement&&n.files?toArray(n.files):n.value}function processInputValue(n,e,t,i,r){if(!(i==null||haveSeenNode(n,i))){if(n.push(i),shouldInclude(i)){let o=getRawAttribute(i,"name");addValueToFormData(o,getValueFromInput(i),e),r&&validateElement(i,t)}i instanceof HTMLFormElement&&(forEach(i.elements,function(o){n.indexOf(o)>=0?removeValueFromFormData(o.name,getValueFromInput(o),e):n.push(o),r&&validateElement(o,t)}),new FormData(i).forEach(function(o,s){o instanceof File&&o.name===""||addValueToFormData(s,o,e)}))}}function validateElement(n,e){let t=n;t.willValidate&&(triggerEvent(t,"htmx:validation:validate"),t.checkValidity()||(triggerEvent(t,"htmx:validation:failed",{message:t.validationMessage,validity:t.validity})&&!e.length&&htmx.config.reportValidityOfForms&&t.reportValidity(),e.push({elt:t,message:t.validationMessage,validity:t.validity})))}function overrideFormData(n,e){for(let t of e.keys())n.delete(t);return e.forEach(function(t,i){n.append(i,t)}),n}function getInputValues(n,e){let t=[],i=new FormData,r=new FormData,o=[],s=getInternalData(n);s.lastButtonClicked&&!bodyContains(s.lastButtonClicked)&&(s.lastButtonClicked=null);let a=n instanceof HTMLFormElement&&n.noValidate!==!0||getAttributeValue(n,"hx-validate")==="true";if(s.lastButtonClicked&&(a=a&&s.lastButtonClicked.formNoValidate!==!0),e!=="get"&&processInputValue(t,r,o,getRelatedForm(n),a),processInputValue(t,i,o,n,a),s.lastButtonClicked||n.tagName==="BUTTON"||n.tagName==="INPUT"&&getRawAttribute(n,"type")==="submit"){let c=s.lastButtonClicked||n,u=getRawAttribute(c,"name");addValueToFormData(u,c.value,r)}let l=findAttributeTargets(n,"hx-include");return forEach(l,function(c){processInputValue(t,i,o,asElement(c),a),matches(c,"form")||forEach(asParentNode(c).querySelectorAll(INPUT_SELECTOR),function(u){processInputValue(t,i,o,u,a)})}),overrideFormData(i,r),{errors:o,formData:i,values:formDataProxy(i)}}function appendParam(n,e,t){n!==""&&(n+="&"),String(t)==="[object Object]"&&(t=JSON.stringify(t));let i=encodeURIComponent(t);return n+=encodeURIComponent(e)+"="+i,n}function urlEncode(n){n=formDataFromObject(n);let e="";return n.forEach(function(t,i){e=appendParam(e,i,t)}),e}function getHeaders(n,e,t){let i={"HX-Request":"true","HX-Trigger":getRawAttribute(n,"id"),"HX-Trigger-Name":getRawAttribute(n,"name"),"HX-Target":getAttributeValue(e,"id"),"HX-Current-URL":location.href};return getValuesForElement(n,"hx-headers",!1,i),t!==void 0&&(i["HX-Prompt"]=t),getInternalData(n).boosted&&(i["HX-Boosted"]="true"),i}function filterValues(n,e){let t=getClosestAttributeValue(e,"hx-params");if(t){if(t==="none")return new FormData;if(t==="*")return n;if(t.indexOf("not ")===0)return forEach(t.slice(4).split(","),function(i){i=i.trim(),n.delete(i)}),n;{let i=new FormData;return forEach(t.split(","),function(r){r=r.trim(),n.has(r)&&n.getAll(r).forEach(function(o){i.append(r,o)})}),i}}else return n}function isAnchorLink(n){return!!getRawAttribute(n,"href")&&getRawAttribute(n,"href").indexOf("#")>=0}function getSwapSpecification(n,e){let t=e||getClosestAttributeValue(n,"hx-swap"),i={swapStyle:getInternalData(n).boosted?"innerHTML":htmx.config.defaultSwapStyle,swapDelay:htmx.config.defaultSwapDelay,settleDelay:htmx.config.defaultSettleDelay};if(htmx.config.scrollIntoViewOnBoost&&getInternalData(n).boosted&&!isAnchorLink(n)&&(i.show="top"),t){let s=splitOnWhitespace(t);if(s.length>0)for(let a=0;a0?r.join(":"):null;i.scroll=u,i.scrollTarget=o}else if(l.indexOf("show:")===0){var r=l.slice(5).split(":");let d=r.pop();var o=r.length>0?r.join(":"):null;i.show=d,i.showTarget=o}else if(l.indexOf("focus-scroll:")===0){let c=l.slice(13);i.focusScroll=c=="true"}else a==0?i.swapStyle=l:logError("Unknown modifier in hx-swap: "+l)}}return i}function usesFormData(n){return getClosestAttributeValue(n,"hx-encoding")==="multipart/form-data"||matches(n,"form")&&getRawAttribute(n,"enctype")==="multipart/form-data"}function encodeParamsForBody(n,e,t){let i=null;return withExtensions(e,function(r){i==null&&(i=r.encodeParameters(n,t,e))}),i!=null?i:usesFormData(e)?overrideFormData(new FormData,formDataFromObject(t)):urlEncode(t)}function makeSettleInfo(n){return{tasks:[],elts:[n]}}function updateScrollState(n,e){let t=n[0],i=n[n.length-1];if(e.scroll){var r=null;e.scrollTarget&&(r=asElement(querySelectorExt(t,e.scrollTarget))),e.scroll==="top"&&(t||r)&&(r=r||t,r.scrollTop=0),e.scroll==="bottom"&&(i||r)&&(r=r||i,r.scrollTop=r.scrollHeight),typeof e.scroll=="number"&&getWindow().setTimeout(function(){window.scrollTo(0,e.scroll)},0)}if(e.show){var r=null;if(e.showTarget){let s=e.showTarget;e.showTarget==="window"&&(s="body"),r=asElement(querySelectorExt(t,s))}e.show==="top"&&(t||r)&&(r=r||t,r.scrollIntoView({block:"start",behavior:htmx.config.scrollBehavior})),e.show==="bottom"&&(i||r)&&(r=r||i,r.scrollIntoView({block:"end",behavior:htmx.config.scrollBehavior}))}}function getValuesForElement(n,e,t,i,r){if(i==null&&(i={}),n==null)return i;let o=getAttributeValue(n,e);if(o){let s=o.trim(),a=t;if(s==="unset")return null;s.indexOf("javascript:")===0?(s=s.slice(11),a=!0):s.indexOf("js:")===0&&(s=s.slice(3),a=!0),s.indexOf("{")!==0&&(s="{"+s+"}");let l;a?l=maybeEval(n,function(){return r?Function("event","return ("+s+")").call(n,r):Function("return ("+s+")").call(n)},{}):l=parseJSON(s);for(let c in l)l.hasOwnProperty(c)&&i[c]==null&&(i[c]=l[c])}return getValuesForElement(asElement(parentElt(n)),e,t,i,r)}function maybeEval(n,e,t){return htmx.config.allowEval?e():(triggerErrorEvent(n,"htmx:evalDisallowedError"),t)}function getHXVarsForElement(n,e,t){return getValuesForElement(n,"hx-vars",!0,t,e)}function getHXValsForElement(n,e,t){return getValuesForElement(n,"hx-vals",!1,t,e)}function getExpressionVars(n,e){return mergeObjects(getHXVarsForElement(n,e),getHXValsForElement(n,e))}function safelySetHeaderValue(n,e,t){if(t!==null)try{n.setRequestHeader(e,t)}catch(i){n.setRequestHeader(e,encodeURIComponent(t)),n.setRequestHeader(e+"-URI-AutoEncoded","true")}}function getPathFromResponse(n){if(n.responseURL)try{let e=new URL(n.responseURL);return e.pathname+e.search}catch(e){triggerErrorEvent(getDocument().body,"htmx:badResponseUrl",{url:n.responseURL})}}function hasHeader(n,e){return e.test(n.getAllResponseHeaders())}function ajaxHelper(n,e,t){if(n=n.toLowerCase(),t){if(t instanceof Element||typeof t=="string")return issueAjaxRequest(n,e,null,null,{targetOverride:resolveTarget(t)||DUMMY_ELT,returnPromise:!0});{let i=resolveTarget(t.target);return(t.target&&!i||t.source&&!i&&!resolveTarget(t.source))&&(i=DUMMY_ELT),issueAjaxRequest(n,e,resolveTarget(t.source),t.event,{handler:t.handler,headers:t.headers,values:t.values,targetOverride:i,swapOverride:t.swap,select:t.select,returnPromise:!0,push:t.push,replace:t.replace,selectOOB:t.selectOOB})}}else return issueAjaxRequest(n,e,null,null,{returnPromise:!0})}function hierarchyForElt(n){let e=[];for(;n;)e.push(n),n=n.parentElement;return e}function verifyPath(n,e,t){let i=new URL(e,location.protocol!=="about:"?location.href:window.origin),o=(location.protocol!=="about:"?location.origin:window.origin)===i.origin;return htmx.config.selfRequestsOnly&&!o?!1:triggerEvent(n,"htmx:validateUrl",mergeObjects({url:i,sameHost:o},t))}function formDataFromObject(n){if(n instanceof FormData)return n;let e=new FormData;for(let t in n)n.hasOwnProperty(t)&&(n[t]&&typeof n[t].forEach=="function"?n[t].forEach(function(i){e.append(t,i)}):typeof n[t]=="object"&&!(n[t]instanceof Blob)?e.append(t,JSON.stringify(n[t])):e.append(t,n[t]));return e}function formDataArrayProxy(n,e,t){return new Proxy(t,{get:function(i,r){return typeof r=="number"?i[r]:r==="length"?i.length:r==="push"?function(o){i.push(o),n.append(e,o)}:typeof i[r]=="function"?function(){i[r].apply(i,arguments),n.delete(e),i.forEach(function(o){n.append(e,o)})}:i[r]&&i[r].length===1?i[r][0]:i[r]},set:function(i,r,o){return i[r]=o,n.delete(e),i.forEach(function(s){n.append(e,s)}),!0}})}function formDataProxy(n){return new Proxy(n,{get:function(e,t){if(typeof t=="symbol"){let r=Reflect.get(e,t);return typeof r=="function"?function(){return r.apply(n,arguments)}:r}if(t==="toJSON")return()=>Object.fromEntries(n);if(t in e&&typeof e[t]=="function")return function(){return n[t].apply(n,arguments)};let i=n.getAll(t);if(i.length!==0)return i.length===1?i[0]:formDataArrayProxy(e,t,i)},set:function(e,t,i){return typeof t!="string"?!1:(e.delete(t),i&&typeof i.forEach=="function"?i.forEach(function(r){e.append(t,r)}):typeof i=="object"&&!(i instanceof Blob)?e.append(t,JSON.stringify(i)):e.append(t,i),!0)},deleteProperty:function(e,t){return typeof t=="string"&&e.delete(t),!0},ownKeys:function(e){return Reflect.ownKeys(Object.fromEntries(e))},getOwnPropertyDescriptor:function(e,t){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e),t)}})}function issueAjaxRequest(n,e,t,i,r,o){let s=null,a=null;if(r=r!=null?r:{},r.returnPromise&&typeof Promise!="undefined")var l=new Promise(function(Y,ee){s=Y,a=ee});t==null&&(t=getDocument().body);let c=r.handler||handleAjaxResponse,u=r.select||null;if(!bodyContains(t))return maybeCall(s),l;let d=r.targetOverride||asElement(getTarget(t));if(d==null||d==DUMMY_ELT)return triggerErrorEvent(t,"htmx:targetError",{target:getClosestAttributeValue(t,"hx-target")}),maybeCall(a),l;let p=getInternalData(t),y=p.lastButtonClicked;if(y){let Y=getRawAttribute(y,"formaction");Y!=null&&(e=Y);let ee=getRawAttribute(y,"formmethod");if(ee!=null)if(VERBS.includes(ee.toLowerCase()))n=ee;else return maybeCall(s),l}let m=getClosestAttributeValue(t,"hx-confirm");if(o===void 0&&triggerEvent(t,"htmx:confirm",{target:d,elt:t,path:e,verb:n,triggeringEvent:i,etc:r,issueRequest:function(oe){return issueAjaxRequest(n,e,t,i,r,!!oe)},question:m})===!1)return maybeCall(s),l;let g=t,_=getClosestAttributeValue(t,"hx-sync"),C=null,O=!1;if(_){let Y=_.split(":"),ee=Y[0].trim();if(ee==="this"?g=findThisElement(t,"hx-sync"):g=asElement(querySelectorExt(t,ee)),_=(Y[1]||"drop").trim(),p=getInternalData(g),_==="drop"&&p.xhr&&p.abortable!==!0)return maybeCall(s),l;if(_==="abort"){if(p.xhr)return maybeCall(s),l;O=!0}else _==="replace"?triggerEvent(g,"htmx:abort"):_.indexOf("queue")===0&&(C=(_.split(" ")[1]||"last").trim())}if(p.xhr)if(p.abortable)triggerEvent(g,"htmx:abort");else{if(C==null){if(i){let Y=getInternalData(i);Y&&Y.triggerSpec&&Y.triggerSpec.queue&&(C=Y.triggerSpec.queue)}C==null&&(C="last")}return p.queuedRequests==null&&(p.queuedRequests=[]),C==="first"&&p.queuedRequests.length===0?p.queuedRequests.push(function(){issueAjaxRequest(n,e,t,i,r)}):C==="all"?p.queuedRequests.push(function(){issueAjaxRequest(n,e,t,i,r)}):C==="last"&&(p.queuedRequests=[],p.queuedRequests.push(function(){issueAjaxRequest(n,e,t,i,r)})),maybeCall(s),l}let w=new XMLHttpRequest;p.xhr=w,p.abortable=O;let T=function(){p.xhr=null,p.abortable=!1,p.queuedRequests!=null&&p.queuedRequests.length>0&&p.queuedRequests.shift()()},$=getClosestAttributeValue(t,"hx-prompt");if($){var j=prompt($);if(j===null||!triggerEvent(t,"htmx:prompt",{prompt:j,target:d}))return maybeCall(s),T(),l}if(m&&!o&&!confirm(m))return maybeCall(s),T(),l;let H=getHeaders(t,d,j);n!=="get"&&!usesFormData(t)&&(H["Content-Type"]="application/x-www-form-urlencoded"),r.headers&&(H=mergeObjects(H,r.headers));let I=getInputValues(t,n),L=I.errors,W=I.formData;r.values&&overrideFormData(W,formDataFromObject(r.values));let G=formDataFromObject(getExpressionVars(t,i)),Q=overrideFormData(W,G),Z=filterValues(Q,t);htmx.config.getCacheBusterParam&&n==="get"&&Z.set("org.htmx.cache-buster",getRawAttribute(d,"id")||"true"),(e==null||e==="")&&(e=location.href);let he=getValuesForElement(t,"hx-request"),Ce=getInternalData(t).boosted,ie=htmx.config.methodsThatUseUrlParams.indexOf(n)>=0,ne={boosted:Ce,useUrlParams:ie,formData:Z,parameters:formDataProxy(Z),unfilteredFormData:Q,unfilteredParameters:formDataProxy(Q),headers:H,elt:t,target:d,verb:n,errors:L,withCredentials:r.credentials||he.credentials||htmx.config.withCredentials,timeout:r.timeout||he.timeout||htmx.config.timeout,path:e,triggeringEvent:i};if(!triggerEvent(t,"htmx:configRequest",ne))return maybeCall(s),T(),l;if(e=ne.path,n=ne.verb,H=ne.headers,Z=formDataFromObject(ne.parameters),L=ne.errors,ie=ne.useUrlParams,L&&L.length>0)return triggerEvent(t,"htmx:validation:halted",ne),maybeCall(s),T(),l;let Ue=e.split("#"),Re=Ue[0],q=Ue[1],M=e;if(ie&&(M=Re,!Z.keys().next().done&&(M.indexOf("?")<0?M+="?":M+="&",M+=urlEncode(Z),q&&(M+="#"+q))),!verifyPath(t,M,ne))return triggerErrorEvent(t,"htmx:invalidPath",ne),maybeCall(a),T(),l;if(w.open(n.toUpperCase(),M,!0),w.overrideMimeType("text/html"),w.withCredentials=ne.withCredentials,w.timeout=ne.timeout,!he.noHeaders){for(let Y in H)if(H.hasOwnProperty(Y)){let ee=H[Y];safelySetHeaderValue(w,Y,ee)}}let A={xhr:w,target:d,requestConfig:ne,etc:r,boosted:Ce,select:u,pathInfo:{requestPath:e,finalRequestPath:M,responsePath:null,anchor:q}};if(w.onload=function(){try{let Y=hierarchyForElt(t);if(A.pathInfo.responsePath=getPathFromResponse(w),c(t,A),A.keepIndicators!==!0&&removeRequestIndicators(B,V),triggerEvent(t,"htmx:afterRequest",A),triggerEvent(t,"htmx:afterOnLoad",A),!bodyContains(t)){let ee=null;for(;Y.length>0&&ee==null;){let oe=Y.shift();bodyContains(oe)&&(ee=oe)}ee&&(triggerEvent(ee,"htmx:afterRequest",A),triggerEvent(ee,"htmx:afterOnLoad",A))}maybeCall(s)}catch(Y){throw triggerErrorEvent(t,"htmx:onLoadError",mergeObjects({error:Y},A)),Y}finally{T()}},w.onerror=function(){removeRequestIndicators(B,V),triggerErrorEvent(t,"htmx:afterRequest",A),triggerErrorEvent(t,"htmx:sendError",A),maybeCall(a),T()},w.onabort=function(){removeRequestIndicators(B,V),triggerErrorEvent(t,"htmx:afterRequest",A),triggerErrorEvent(t,"htmx:sendAbort",A),maybeCall(a),T()},w.ontimeout=function(){removeRequestIndicators(B,V),triggerErrorEvent(t,"htmx:afterRequest",A),triggerErrorEvent(t,"htmx:timeout",A),maybeCall(a),T()},!triggerEvent(t,"htmx:beforeRequest",A))return maybeCall(s),T(),l;var B=addRequestIndicatorClasses(t),V=disableElements(t);forEach(["loadstart","loadend","progress","abort"],function(Y){forEach([w,w.upload],function(ee){ee.addEventListener(Y,function(oe){triggerEvent(t,"htmx:xhr:"+Y,{lengthComputable:oe.lengthComputable,loaded:oe.loaded,total:oe.total})})})}),triggerEvent(t,"htmx:beforeSend",A);let U=ie?null:encodeParamsForBody(w,t,Z);return w.send(U),l}function determineHistoryUpdates(n,e){let t=e.xhr,i=null,r=null;if(hasHeader(t,/HX-Push:/i)?(i=t.getResponseHeader("HX-Push"),r="push"):hasHeader(t,/HX-Push-Url:/i)?(i=t.getResponseHeader("HX-Push-Url"),r="push"):hasHeader(t,/HX-Replace-Url:/i)&&(i=t.getResponseHeader("HX-Replace-Url"),r="replace"),i)return i==="false"?{}:{type:r,path:i};let o=e.pathInfo.finalRequestPath,s=e.pathInfo.responsePath,a=e.etc.push||getClosestAttributeValue(n,"hx-push-url"),l=e.etc.replace||getClosestAttributeValue(n,"hx-replace-url");a==="false"&&(a=null),l==="false"&&(l=null);let c=getInternalData(n).boosted,u=null,d=null;return a?(u="push",d=a):l?(u="replace",d=l):c&&(u="push",d=s||o),d?(d==="true"&&(d=s||o),e.pathInfo.anchor&&d.indexOf("#")===-1&&(d=d+"#"+e.pathInfo.anchor),{type:u,path:d}):{}}function codeMatches(n,e){var t=new RegExp(n.code);return t.test(e.toString(10))}function resolveResponseHandling(n){for(var e=0;e.${e}{opacity:0;visibility: hidden} .${t} .${e}, .${t}.${e}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}`)}}function getMetaConfig(){let n=getDocument().querySelector('meta[name="htmx-config"]');return n?parseJSON(n.content):null}function mergeMetaConfig(){let n=getMetaConfig();n&&(htmx.config=mergeObjects(htmx.config,n))}return ready(function(){mergeMetaConfig(),insertIndicatorStyles();let n=getDocument().body;processNode(n);let e=getDocument().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");n.addEventListener("htmx:abort",function(i){let r=i.detail.elt||i.target,o=getInternalData(r);o&&o.xhr&&o.xhr.abort()});let t=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(i){i.state&&i.state.htmx?(restoreHistory(),forEach(e,function(r){triggerEvent(r,"htmx:restored",{document:getDocument(),triggerEvent})})):t&&t(i)},getWindow().setTimeout(function(){triggerEvent(n,"htmx:load",{}),n=null},0)}),htmx})(),I_=Qp;function Mo(n,e){n.split(/\s+/).forEach(t=>{e(t)})}var ni=class{constructor(){this._events={}}on(e,t){Mo(e,i=>{let r=this._events[i]||[];r.push(t),this._events[i]=r})}off(e,t){var i=arguments.length;if(i===0){this._events={};return}Mo(e,r=>{if(i===1){delete this._events[r];return}let o=this._events[r];o!==void 0&&(o.splice(o.indexOf(t),1),this._events[r]=o)})}trigger(e,...t){var i=this;Mo(e,r=>{let o=i._events[r];o!==void 0&&o.forEach(s=>{s.apply(i,t)})})}};function No(n){return n.plugins={},class extends n{constructor(){super(...arguments),this.plugins={names:[],settings:{},requested:{},loaded:{}}}static define(e,t){n.plugins[e]={name:e,fn:t}}initializePlugins(e){var t,i;let r=this,o=[];if(Array.isArray(e))e.forEach(s=>{typeof s=="string"?o.push(s):(r.plugins.settings[s.name]=s.options,o.push(s.name))});else if(e)for(t in e)e.hasOwnProperty(t)&&(r.plugins.settings[t]=e[t],o.push(t));for(;i=o.shift();)r.require(i)}loadPlugin(e){var t=this,i=t.plugins,r=n.plugins[e];if(!n.plugins.hasOwnProperty(e))throw new Error('Unable to find "'+e+'" plugin');i.requested[e]=!0,i.loaded[e]=r.fn.apply(t,[t.plugins.settings[e]||{}]),i.names.push(e)}require(e){var t=this,i=t.plugins;if(!t.plugins.loaded.hasOwnProperty(e)){if(i.requested[e])throw new Error('Plugin has circular dependency ("'+e+'")');t.loadPlugin(e)}return i.loaded[e]}}}var ii=n=>(n=n.filter(Boolean),n.length<2?n[0]||"":Zp(n)==1?"["+n.join("")+"]":"(?:"+n.join("|")+")"),ko=n=>{if(!Jp(n))return n.join("");let e="",t=0,i=()=>{t>1&&(e+="{"+t+"}")};return n.forEach((r,o)=>{if(r===n[o-1]){t++;return}i(),e+=r,t=1}),i(),e},Ho=n=>{let e=Array.from(n);return ii(e)},Jp=n=>new Set(n).size!==n.length,Lt=n=>(n+"").replace(/([\$\(\)\*\+\.\?\[\]\^\{\|\}\\])/gu,"\\$1"),Zp=n=>n.reduce((e,t)=>Math.max(e,em(t)),0),em=n=>Array.from(n).length;var Io=n=>{if(n.length===1)return[[n]];let e=[],t=n.substring(1);return Io(t).forEach(function(r){let o=r.slice(0);o[0]=n.charAt(0)+o[0],e.push(o),o=r.slice(0),o.unshift(n.charAt(0)),e.push(o)}),e};var tm=[[0,65535]],nm="[\u0300-\u036F\xB7\u02BE\u02BC]",nr,Ba,im=3,Ro={},Pa={"/":"\u2044\u2215",0:"\u07C0",a:"\u2C65\u0250\u0251",aa:"\uA733",ae:"\xE6\u01FD\u01E3",ao:"\uA735",au:"\uA737",av:"\uA739\uA73B",ay:"\uA73D",b:"\u0180\u0253\u0183",c:"\uA73F\u0188\u023C\u2184",d:"\u0111\u0257\u0256\u1D05\u018C\uABB7\u0501\u0266",e:"\u025B\u01DD\u1D07\u0247",f:"\uA77C\u0192",g:"\u01E5\u0260\uA7A1\u1D79\uA77F\u0262",h:"\u0127\u2C68\u2C76\u0265",i:"\u0268\u0131",j:"\u0249\u0237",k:"\u0199\u2C6A\uA741\uA743\uA745\uA7A3",l:"\u0142\u019A\u026B\u2C61\uA749\uA747\uA781\u026D",m:"\u0271\u026F\u03FB",n:"\uA7A5\u019E\u0272\uA791\u1D0E\u043B\u0509",o:"\xF8\u01FF\u0254\u0275\uA74B\uA74D\u1D11",oe:"\u0153",oi:"\u01A3",oo:"\uA74F",ou:"\u0223",p:"\u01A5\u1D7D\uA751\uA753\uA755\u03C1",q:"\uA757\uA759\u024B",r:"\u024D\u027D\uA75B\uA7A7\uA783",s:"\xDF\u023F\uA7A9\uA785\u0282",t:"\u0167\u01AD\u0288\u2C66\uA787",th:"\xFE",tz:"\uA729",u:"\u0289",v:"\u028B\uA75F\u028C",vy:"\uA761",w:"\u2C73",y:"\u01B4\u024F\u1EFF",z:"\u01B6\u0225\u0240\u2C6C\uA763",hv:"\u0195"};for(let n in Pa){let e=Pa[n]||"";for(let t=0;t{nr===void 0&&(nr=cm(n||tm))},Fa=(n,e="NFKD")=>n.normalize(e),ri=n=>Array.from(n).reduce((e,t)=>e+sm(t),""),sm=n=>(n=Fa(n).toLowerCase().replace(rm,e=>Ro[e]||""),Fa(n,"NFC"));function*am(n){for(let[e,t]of n)for(let i=e;i<=t;i++){let r=String.fromCharCode(i),o=ri(r);o!=r.toLowerCase()&&(o.length>im||o.length!=0&&(yield{folded:o,composed:r,code_point:i}))}}var lm=n=>{let e={},t=(i,r)=>{let o=e[i]||new Set,s=new RegExp("^"+Ho(o)+"$","iu");r.match(s)||(o.add(Lt(r)),e[i]=o)};for(let i of am(n))t(i.folded,i.folded),t(i.folded,i.composed);return e},cm=n=>{let e=lm(n),t={},i=[];for(let o in e){let s=e[o];s&&(t[o]=Ho(s)),o.length>1&&i.push(Lt(o))}i.sort((o,s)=>s.length-o.length);let r=ii(i);return Ba=new RegExp("^"+r,"u"),t},um=(n,e=1)=>{let t=0;return n=n.map(i=>(nr[i]&&(t+=i.length),nr[i]||i)),t>=e?ko(n):""},dm=(n,e=1)=>(e=Math.max(e,n.length-1),ii(Io(n).map(t=>um(t,e)))),$a=(n,e=!0)=>{let t=n.length>1?1:0;return ii(n.map(i=>{let r=[],o=e?i.length():i.length()-1;for(let s=0;s{for(let t of e){if(t.start!=n.start||t.end!=n.end||t.substrs.join("")!==n.substrs.join(""))continue;let i=n.parts,r=s=>{for(let a of i){if(a.start===s.start&&a.substr===s.substr)return!1;if(!(s.length==1||a.length==1)&&(s.starta.start||a.starts.start))return!0}return!1};if(!(t.parts.filter(r).length>0))return!0}return!1},ir=class n{constructor(){le(this,"parts");le(this,"substrs");le(this,"start");le(this,"end");this.parts=[],this.substrs=[],this.start=0,this.end=0}add(e){e&&(this.parts.push(e),this.substrs.push(e.substr),this.start=Math.min(e.start,this.start),this.end=Math.max(e.end,this.end))}last(){return this.parts[this.parts.length-1]}length(){return this.parts.length}clone(e,t){let i=new n,r=JSON.parse(JSON.stringify(this.parts)),o=r.pop();for(let l of r)i.add(l);let s=t.substr.substring(0,e-o.start),a=s.length;return i.add({start:o.start,end:o.start+a,length:a,substr:s}),i}},Va=n=>{om(),n=ri(n);let e="",t=[new ir];for(let i=0;i0){l=l.sort((u,d)=>u.length()-d.length());for(let u of l)fm(u,t)||t.push(u);continue}if(i>0&&c.size==1&&!c.has("3")){e+=$a(t,!1);let u=new ir,d=t[0];d&&u.add(d.last()),t=[u]}}return e+=$a(t,!0),e};var za=(n,e)=>{if(n)return n[e]},ja=(n,e)=>{if(n){for(var t,i=e.split(".");(t=i.shift())&&(n=n[t]););return n}},rr=(n,e,t)=>{var i,r;return!n||(n=n+"",e.regex==null)||(r=n.search(e.regex),r===-1)?0:(i=e.string.length/n.length,r===0&&(i+=.5),i*t)},or=(n,e)=>{var t=n[e];if(typeof t=="function")return t;t&&!Array.isArray(t)&&(n[e]=[t])},oi=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},Wa=(n,e)=>typeof n=="number"&&typeof e=="number"?n>e?1:ne?1:e>n?-1:0);var sr=class{constructor(e,t){le(this,"items");le(this,"settings");this.items=e,this.settings=t||{diacritics:!0}}tokenize(e,t,i){if(!e||!e.length)return[];let r=[],o=e.split(/\s+/);var s;return i&&(s=new RegExp("^("+Object.keys(i).map(Lt).join("|")+"):(.*)$")),o.forEach(a=>{let l,c=null,u=null;s&&(l=a.match(s))&&(c=l[1],a=l[2]),a.length>0&&(this.settings.diacritics?u=Va(a)||null:u=Lt(a),u&&t&&(u="\\b"+u)),r.push({string:a,regex:u?new RegExp(u,"iu"):null,field:c})}),r}getScoreFunction(e,t){var i=this.prepareSearch(e,t);return this._getScoreFunction(i)}_getScoreFunction(e){let t=e.tokens,i=t.length;if(!i)return function(){return 0};let r=e.options.fields,o=e.weights,s=r.length,a=e.getAttrFn;if(!s)return function(){return 1};let l=(function(){return s===1?function(c,u){let d=r[0].field;return rr(a(u,d),c,o[d]||1)}:function(c,u){var d=0;if(c.field){let p=a(u,c.field);!c.regex&&p?d+=1/s:d+=rr(p,c,1)}else oi(o,(p,y)=>{d+=rr(a(u,y),c,p)});return d/s}})();return i===1?function(c){return l(t[0],c)}:e.options.conjunction==="and"?function(c){var u,d=0;for(let p of t){if(u=l(p,c),u<=0)return 0;d+=u}return d/i}:function(c){var u=0;return oi(t,d=>{u+=l(d,c)}),u/i}}getSortFunction(e,t){var i=this.prepareSearch(e,t);return this._getSortFunction(i)}_getSortFunction(e){var t,i=[];let r=this,o=e.options,s=!e.query&&o.sort_empty?o.sort_empty:o.sort;if(typeof s=="function")return s.bind(this);let a=function(c,u){return c==="$score"?u.score:e.getAttrFn(r.items[u.id],c)};if(s)for(let c of s)(e.query||c.field!=="$score")&&i.push(c);if(e.query){t=!0;for(let c of i)if(c.field==="$score"){t=!1;break}t&&i.unshift({field:"$score",direction:"desc"})}else i=i.filter(c=>c.field!=="$score");return i.length?function(c,u){var d,p;for(let y of i)if(p=y.field,d=(y.direction==="desc"?-1:1)*Wa(a(p,c),a(p,u)),d)return d;return 0}:null}prepareSearch(e,t){let i={};var r=Object.assign({},t);if(or(r,"sort"),or(r,"sort_empty"),r.fields){or(r,"fields");let o=[];r.fields.forEach(s=>{typeof s=="string"&&(s={field:s,weight:1}),o.push(s),i[s.field]="weight"in s?s.weight:1}),r.fields=o}return{options:r,query:e.toLowerCase().trim(),tokens:this.tokenize(e,r.respect_word_boundaries,i),total:0,items:[],weights:i,getAttrFn:r.nesting?ja:za}}search(e,t){var i=this,r,o;o=this.prepareSearch(e,t),t=o.options,e=o.query;let s=t.score||i._getScoreFunction(o);e.length?oi(i.items,(l,c)=>{r=s(l),(t.filter===!1||r>0)&&o.items.push({score:r,id:c})}):oi(i.items,(l,c)=>{o.items.push({score:1,id:c})});let a=i._getSortFunction(o);return a&&o.items.sort(a),o.total=o.items.length,typeof t.limit=="number"&&(o.items=o.items.slice(0,t.limit)),o}};var Fe=n=>typeof n=="undefined"||n===null?null:si(n),si=n=>typeof n=="boolean"?n?"1":"0":n+"",ar=n=>(n+"").replace(/&/g,"&").replace(//g,">").replace(/"/g,"""),qa=(n,e)=>e>0?window.setTimeout(n,e):(n.call(null),null),Ua=(n,e)=>{var t;return function(i,r){var o=this;t&&(o.loading=Math.max(o.loading-1,0),clearTimeout(t)),t=setTimeout(function(){t=null,o.loadedSearches[i]=!0,n.call(o,i,r)},e)}},Po=(n,e,t)=>{var i,r=n.trigger,o={};n.trigger=function(){var s=arguments[0];if(e.indexOf(s)!==-1)o[s]=arguments;else return r.apply(n,arguments)},t.apply(n,[]),n.trigger=r;for(i of e)i in o&&r.apply(n,o[i])},Ya=n=>({start:n.selectionStart||0,length:(n.selectionEnd||0)-(n.selectionStart||0)}),fe=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},De=(n,e,t,i)=>{n.addEventListener(e,t,i)},Mt=(n,e)=>{if(!e||!e[n])return!1;var t=(e.altKey?1:0)+(e.ctrlKey?1:0)+(e.shiftKey?1:0)+(e.metaKey?1:0);return t===1},lr=(n,e)=>{let t=n.getAttribute("id");return t||(n.setAttribute("id",e),e)},Fo=n=>n.replace(/[\\"']/g,"\\$&"),Nt=(n,e)=>{e&&n.append(e)},ve=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)};var nt=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if($o(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},$o=n=>typeof n=="string"&&n.indexOf("<")>-1,Ga=n=>n.replace(/['"\\]/g,"\\$&"),cr=(n,e)=>{var t=document.createEvent("HTMLEvents");t.initEvent(e,!0,!1),n.dispatchEvent(t)},ai=(n,e)=>{Object.assign(n.style,e)},$e=(n,...e)=>{var t=Ka(e);n=Xa(n),n.map(i=>{t.map(r=>{i.classList.add(r)})})},gt=(n,...e)=>{var t=Ka(e);n=Xa(n),n.map(i=>{t.map(r=>{i.classList.remove(r)})})},Ka=n=>{var e=[];return ve(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Xa=n=>(Array.isArray(n)||(n=[n]),n),ur=(n,e,t)=>{if(!(t&&!t.contains(n)))for(;n&&n.matches;){if(n.matches(e))return n;n=n.parentNode}},Bo=(n,e=0)=>e>0?n[n.length-1]:n[0],Qa=n=>Object.keys(n).length===0,Vo=(n,e)=>{if(!n)return-1;e=e||n.nodeName;for(var t=0;n=n.previousElementSibling;)n.matches(e)&&t++;return t},ae=(n,e)=>{ve(e,(t,i)=>{t==null?n.removeAttribute(i):n.setAttribute(i,""+t)})},li=(n,e)=>{n.parentNode&&n.parentNode.replaceChild(e,n)};var Ja=(n,e)=>{if(e===null)return;if(typeof e=="string"){if(!e.length)return;e=new RegExp(e,"i")}let t=o=>{var s=o.data.match(e);if(s&&o.data.length>0){var a=document.createElement("span");a.className="highlight";var l=o.splitText(s.index);l.splitText(s[0].length);var c=l.cloneNode(!0);return a.appendChild(c),li(l,a),1}return 0},i=o=>{o.nodeType===1&&o.childNodes&&!/(script|style)/i.test(o.tagName)&&(o.className!=="highlight"||o.tagName!=="SPAN")&&Array.from(o.childNodes).forEach(s=>{r(s)})},r=o=>o.nodeType===3?t(o):(i(o),0);r(n)},Za=n=>{var e=n.querySelectorAll("span.highlight");Array.prototype.forEach.call(e,function(t){var i=t.parentNode;i.replaceChild(t.firstChild,t),i.normalize()})};var hm=typeof navigator=="undefined"?!1:/Mac/.test(navigator.userAgent),ci=hm?"metaKey":"ctrlKey";var zo={options:[],optgroups:[],plugins:[],delimiter:",",splitOn:null,persist:!0,diacritics:!0,create:null,createOnBlur:!1,createFilter:null,clearAfterSelect:!1,highlight:!0,openOnFocus:!0,shouldOpen:null,maxOptions:50,maxItems:null,hideSelected:null,duplicates:!1,addPrecedence:!1,selectOnTab:!1,preload:null,allowEmptyOption:!1,refreshThrottle:300,loadThrottle:300,loadingClass:"loading",dataAttr:null,optgroupField:"optgroup",valueField:"value",labelField:"text",disabledField:"disabled",optgroupLabelField:"label",optgroupValueField:"value",lockOptgroupOrder:!1,sortField:"$order",searchField:["text"],searchConjunction:"and",mode:null,wrapperClass:"ts-wrapper",controlClass:"ts-control",dropdownClass:"ts-dropdown",dropdownContentClass:"ts-dropdown-content",itemClass:"item",optionClass:"option",dropdownParent:null,controlInput:'',copyClassesToDropdown:!1,placeholder:null,hidePlaceholder:null,shouldLoad:function(n){return n.length>0},render:{}};function dr(n,e){var t=Object.assign({},zo,e),i=t.dataAttr,r=t.labelField,o=t.valueField,s=t.disabledField,a=t.optgroupField,l=t.optgroupLabelField,c=t.optgroupValueField,u=n.tagName.toLowerCase(),d=n.getAttribute("placeholder")||n.getAttribute("data-placeholder");if(!d&&!t.allowEmptyOption){let g=n.querySelector('option[value=""]');g&&(d=g.textContent)}var p={placeholder:d,options:[],optgroups:[],items:[],maxItems:null},y=()=>{var g,_=p.options,C={},O=1;let w=0;var T=H=>{var I=Object.assign({},H.dataset),L=i&&I[i];return typeof L=="string"&&L.length&&(I=Object.assign(I,JSON.parse(L))),I},$=(H,I)=>{var L=Fe(H.value);if(L!=null&&!(!L&&!t.allowEmptyOption)){if(C.hasOwnProperty(L)){if(I){var W=C[L][a];W?Array.isArray(W)?W.push(I):C[L][a]=[W,I]:C[L][a]=I}}else{var G=T(H);G[r]=G[r]||H.textContent,G[o]=G[o]||L,G[s]=G[s]||H.disabled,G[a]=G[a]||I,G.$option=H,G.$order=G.$order||++w,C[L]=G,_.push(G)}H.selected&&p.items.push(L)}},j=H=>{var I,L;L=T(H),L[l]=L[l]||H.getAttribute("label")||"",L[c]=L[c]||O++,L[s]=L[s]||H.disabled,L.$order=L.$order||++w,p.optgroups.push(L),I=L[c],ve(H.children,W=>{$(W,I)})};p.maxItems=n.hasAttribute("multiple")?null:1,ve(n.children,H=>{g=H.tagName.toLowerCase(),g==="optgroup"?j(H):g==="option"&&$(H)})},m=()=>{var g,_;let C=n.getAttribute(i);if(C)p.options=JSON.parse(C),ve(p.options,w=>{p.items.push(w[o])});else{var O=(_=(g=n==null?void 0:n.value)===null||g===void 0?void 0:g.trim())!==null&&_!==void 0?_:"";if(!t.allowEmptyOption&&!O.length)return;let w=O.split(t.delimiter);ve(w,T=>{let $={};$[r]=T,$[o]=T,p.options.push($)}),p.items=w}};return u==="select"?y():m(),Object.assign({},zo,p,e)}var nl=0,we=class extends No(ni){constructor(e,t){super(),this.order=0,this.isOpen=!1,this.isDisabled=!1,this.isReadOnly=!1,this.isInvalid=!1,this.isValid=!0,this.isLocked=!1,this.isFocused=!1,this.isInputHidden=!1,this.isSetup=!1,this.isDropdownContentStale=!0,this.ignoreFocus=!1,this.ignoreHover=!1,this.hasOptions=!1,this.lastValue="",this.caretPos=0,this.loading=0,this.loadedSearches={},this.activeOption=null,this.activeItems=[],this.optgroups={},this.options={},this.userOptions={},this.items=[],this.refreshTimeout=null,nl++;var i,r=nt(e);if(r.tomselect)throw new Error("Tom Select already initialized on this element");r.tomselect=this;var o=window.getComputedStyle&&window.getComputedStyle(r,null);i=o.getPropertyValue("direction");let s=dr(r,t);this.settings=s,this.input=r,this.tabIndex=r.tabIndex||0,this.is_select_tag=r.tagName.toLowerCase()==="select",this.rtl=/rtl/i.test(i),this.inputId=lr(r,"tomselect-"+nl),this.isRequired=r.required,this.sifter=new sr(this.options,{diacritics:s.diacritics}),s.mode=s.mode||(s.maxItems===1?"single":"multi"),typeof s.hideSelected!="boolean"&&(s.hideSelected=s.mode==="multi"),typeof s.hidePlaceholder!="boolean"&&(s.hidePlaceholder=s.mode!=="multi");var a=s.createFilter;typeof a!="function"&&(typeof a=="string"&&(a=new RegExp(a)),a instanceof RegExp?s.createFilter=_=>a.test(_):s.createFilter=_=>this.settings.duplicates||!this.options[_]),this.initializePlugins(s.plugins),this.setupCallbacks(),this.setupTemplates();let l=nt("
"),c=nt("
"),u=this._render("dropdown"),d=nt('
'),p=this.input.getAttribute("class")||"",y=s.mode;var m;if($e(l,s.wrapperClass,p,y),$e(c,s.controlClass),Nt(l,c),$e(u,s.dropdownClass,y),s.copyClassesToDropdown&&$e(u,p),$e(d,s.dropdownContentClass),Nt(u,d),nt(s.dropdownParent||l).appendChild(u),$o(s.controlInput)){m=nt(s.controlInput);var g=["autocorrect","autocapitalize","autocomplete","spellcheck","aria-label"];ve(g,_=>{r.getAttribute(_)&&ae(m,{[_]:r.getAttribute(_)})}),m.tabIndex=-1,c.appendChild(m),this.focus_node=m}else s.controlInput?(m=nt(s.controlInput),this.focus_node=m):(m=nt(""),this.focus_node=c);this.wrapper=l,this.dropdown=u,this.dropdown_content=d,this.control=c,this.control_input=m,this.setup()}setup(){let e=this,t=e.settings,i=e.control_input,r=e.dropdown,o=e.dropdown_content,s=e.wrapper,a=e.control,l=e.input,c=e.focus_node,u={passive:!0},d=e.inputId+"-ts-dropdown";ae(o,{id:d}),ae(c,{role:"combobox","aria-haspopup":"listbox","aria-expanded":"false","aria-controls":d});let p=lr(c,e.inputId+"-ts-control"),y="label[for='"+Ga(e.inputId)+"']",m=document.querySelector(y),g=e.focus.bind(e);if(m){De(m,"click",g),ae(m,{for:p});let w=lr(m,e.inputId+"-ts-label");ae(c,{"aria-labelledby":w}),ae(o,{"aria-labelledby":w})}if(s.style.width=l.style.width,s.style.minWidth=l.style.minWidth,s.style.maxWidth=l.style.maxWidth,e.plugins.names.length){let w="plugin-"+e.plugins.names.join(" plugin-");$e([s,r],w)}(t.maxItems===null||t.maxItems>1)&&e.is_select_tag&&ae(l,{multiple:"multiple"}),t.placeholder&&ae(i,{placeholder:t.placeholder}),!t.splitOn&&t.delimiter&&(t.splitOn=new RegExp("\\s*"+Lt(t.delimiter)+"+\\s*")),t.load&&t.loadThrottle&&(t.load=Ua(t.load,t.loadThrottle)),De(r,"mousemove",()=>{e.ignoreHover=!1}),De(r,"mouseenter",w=>{var T=ur(w.target,"[data-selectable]",r);T&&e.onOptionHover(w,T)},{capture:!0}),De(r,"click",w=>{let T=ur(w.target,"[data-selectable]");T&&(e.onOptionSelect(w,T),fe(w,!0))}),De(a,"click",w=>{var T=ur(w.target,"[data-ts-item]",a);if(T&&e.onItemSelect(w,T)){fe(w,!0);return}i.value==""&&(e.onClick(),fe(w,!0))}),De(c,"keydown",w=>e.onKeyDown(w)),De(i,"keypress",w=>e.onKeyPress(w)),De(i,"input",w=>e.onInput(w)),De(c,"blur",w=>e.onBlur(w)),De(c,"focus",w=>e.onFocus(w)),De(i,"paste",w=>e.onPaste(w));let _=w=>{let T=w.composedPath()[0];if(!s.contains(T)&&!r.contains(T)){e.isFocused&&e.blur(),e.inputState();return}T==i&&e.isOpen?w.stopPropagation():fe(w,!0)},C=()=>{e.isOpen&&e.positionDropdown()},O=()=>{e.isValid&&(e.isValid=!1,e.isInvalid=!0,e.refreshState())};De(l,"invalid",O),De(document,"mousedown",_),De(window,"scroll",C,u),De(window,"resize",C,u),this._destroy=()=>{l.removeEventListener("invalid",O),document.removeEventListener("mousedown",_),window.removeEventListener("scroll",C),window.removeEventListener("resize",C),m&&m.removeEventListener("click",g)},this.revertSettings={innerHTML:l.innerHTML,tabIndex:l.tabIndex},l.tabIndex=-1,l.insertAdjacentElement("afterend",e.wrapper),e.sync(!1),t.items=[],delete t.optgroups,delete t.options,e.refreshItems(),e.close(!1),e.inputState(),e.isSetup=!0,e.on("change",this.onChange),$e(l,"tomselected","ts-hidden-accessible"),e.trigger("initialize"),t.preload===!0&&e.preload()}setupOptions(e=[],t=[]){this.addOptions(e),ve(t,i=>{this.registerOptionGroup(i)})}setupTemplates(){var e=this,t=e.settings.labelField,i=e.settings.optgroupLabelField,r={optgroup:o=>{let s=document.createElement("div");return s.className="optgroup",s.appendChild(o.options),s},optgroup_header:(o,s)=>'
'+s(o[i])+"
",option:(o,s)=>"
"+s(o[t])+"
",item:(o,s)=>"
"+s(o[t])+"
",option_create:(o,s)=>'
Add '+s(o.input)+"
",no_results:()=>'
No results found
',loading:()=>'
',not_loading:()=>{},dropdown:()=>"
"};e.settings.render=Object.assign({},r,e.settings.render)}setupCallbacks(){var e,t,i={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",item_select:"onItemSelect",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(e in i)t=this.settings[i[e]],t&&this.on(e,t)}sync(e=!0){let t=this,i=e?dr(t.input,{delimiter:t.settings.delimiter,allowEmptyOption:t.settings.allowEmptyOption}):t.settings;t.setupOptions(i.options,i.optgroups),t.setValue(i.items||[],!0),t.input.disabled?t.disable():t.input.readOnly?t.setReadOnly(!0):t.enable(),t.lastQuery=null}onClick(){var e=this;if(e.activeItems.length>0){e.clearActiveItems(),e.focus();return}e.isFocused&&e.isOpen?e.blur():e.focus()}onMouseDown(){}onChange(){cr(this.input,"input"),cr(this.input,"change")}onPaste(e){var t=this;if(t.isInputHidden||t.isLocked){fe(e);return}t.settings.splitOn&&setTimeout(()=>{var i=t.inputValue();if(i.match(t.settings.splitOn)){var r=i.trim().split(t.settings.splitOn);ve(r,o=>{Fe(o)&&(this.options[o]?t.addItem(o):t.createItem(o))})}},0)}onKeyPress(e){var t=this;if(t.isLocked){fe(e);return}var i=String.fromCharCode(e.keyCode||e.which);if(t.settings.create&&t.settings.mode==="multi"&&i===t.settings.delimiter){t.createItem(),fe(e);return}}onKeyDown(e){var t=this;if(t.ignoreHover=!0,t.isLocked){e.keyCode!==9&&fe(e);return}switch(e.keyCode){case 65:if(Mt(ci,e)&&t.control_input.value==""){fe(e),t.selectAll();return}break;case 27:t.isOpen&&(fe(e,!0),t.close()),t.clearActiveItems();return;case 40:if(!t.isOpen&&t.hasOptions)t.open();else if(t.activeOption){let i=t.getAdjacent(t.activeOption,1);i&&t.setActiveOption(i)}fe(e);return;case 38:if(t.activeOption){let i=t.getAdjacent(t.activeOption,-1);i&&t.setActiveOption(i)}fe(e);return;case 13:t.canSelect(t.activeOption)?(t.onOptionSelect(e,t.activeOption),fe(e)):t.settings.create&&t.createItem()?fe(e):document.activeElement==t.control_input&&t.isOpen&&fe(e);return;case 37:t.advanceSelection(-1,e);return;case 39:t.advanceSelection(1,e);return;case 9:t.settings.selectOnTab&&(t.canSelect(t.activeOption)?(t.onOptionSelect(e,t.activeOption),fe(e)):t.settings.create&&t.createItem()&&fe(e));return;case 8:case 46:t.deleteSelection(e);return}t.isInputHidden&&!Mt(ci,e)&&fe(e)}onInput(e){if(this.isLocked)return;let t=this.inputValue();if(this.lastValue!==t){if(this.lastValue=t,t==""){this._onInput();return}this.refreshTimeout&&window.clearTimeout(this.refreshTimeout),this.refreshTimeout=qa(()=>{this.refreshTimeout=null,this._onInput()},this.settings.refreshThrottle)}}_onInput(){let e=this.lastValue;this.settings.shouldLoad.call(this,e)&&this.load(e),this.refreshOptions(),this.trigger("type",e)}onOptionHover(e,t){this.ignoreHover||this.setActiveOption(t,!1)}onFocus(e){var t=this,i=t.isFocused;if(t.isDisabled||t.isReadOnly){t.blur(),fe(e);return}t.ignoreFocus||(t.isFocused=!0,t.settings.preload==="focus"&&t.preload(),i||t.trigger("focus"),t.activeItems.length||(t.inputState(),t.refreshOptions(!!t.settings.openOnFocus)),t.refreshState())}onBlur(e){if(document.hasFocus()!==!1){var t=this;if(t.isFocused){t.isFocused=!1,t.ignoreFocus=!1;var i=()=>{t.close(),t.setActiveItem(),t.setCaret(t.items.length),t.trigger("blur")};t.settings.create&&t.settings.createOnBlur?t.createItem(null,i):i()}}}onOptionSelect(e,t){var i,r=this;t.parentElement&&t.parentElement.matches("[data-disabled]")||(t.classList.contains("create")?r.createItem(null,()=>{r.settings.closeAfterSelect?r.close():r.settings.clearAfterSelect&&r.setTextboxValue()}):(i=t.dataset.value,typeof i!="undefined"&&(r.isDropdownContentStale=r.settings.hideSelected,r.addItem(i),r.settings.closeAfterSelect?r.close():r.settings.clearAfterSelect&&r.setTextboxValue(),!r.settings.hideSelected&&e.type&&/click/.test(e.type)&&r.setActiveOption(t))))}canSelect(e){return!!(this.isOpen&&e&&this.dropdown_content.contains(e))}onItemSelect(e,t){var i=this;return!i.isLocked&&i.settings.mode==="multi"?(fe(e),i.setActiveItem(t,e),!0):!1}canLoad(e){return!(!this.settings.load||this.loadedSearches.hasOwnProperty(e))}load(e){let t=this;if(!t.canLoad(e))return;$e(t.wrapper,t.settings.loadingClass),t.loading++;let i=t.loadCallback.bind(t);t.settings.load.call(t,e,i)}loadCallback(e,t){let i=this;i.loading=Math.max(i.loading-1,0),i.isDropdownContentStale=!0,i.clearActiveOption(),i.setupOptions(e,t),i.refreshOptions(i.isFocused&&!i.isInputHidden),i.loading||gt(i.wrapper,i.settings.loadingClass),i.trigger("load",e,t)}preload(){var e=this.wrapper.classList;e.contains("preloaded")||(e.add("preloaded"),this.load(""))}setTextboxValue(e=""){var t=this.control_input,i=t.value!==e;i&&(t.value=e,cr(t,"update"),this.lastValue=e)}getValue(){return this.is_select_tag&&this.input.hasAttribute("multiple")?this.items:this.items.join(this.settings.delimiter)}setValue(e,t){var i=t?[]:["change"];Po(this,i,()=>{this.clear(t),this.addItems(e,t)})}setMaxItems(e){e===0&&(e=null),this.settings.maxItems=e,this.refreshState()}setActiveItem(e,t){var i=this,r,o,s,a,l,c;if(i.settings.mode!=="single"){if(!e){i.clearActiveItems(),i.isFocused&&i.inputState();return}if(r=t&&t.type.toLowerCase(),r==="click"&&Mt("shiftKey",t)&&i.activeItems.length){for(c=i.getLastActive(),s=Array.prototype.indexOf.call(i.control.children,c),a=Array.prototype.indexOf.call(i.control.children,e),s>a&&(l=s,s=a,a=l),o=s;o<=a;o++)e=i.control.children[o],i.activeItems.indexOf(e)===-1&&i.setActiveItemClass(e);fe(t)}else r==="click"&&Mt(ci,t)||r==="keydown"&&Mt("shiftKey",t)?e.classList.contains("active")?i.removeActiveItem(e):i.setActiveItemClass(e):(i.clearActiveItems(),i.setActiveItemClass(e));i.inputState(),i.isFocused||i.focus()}}setActiveItemClass(e){let t=this,i=t.control.querySelector(".last-active");i&>(i,"last-active"),$e(e,"active last-active"),t.trigger("item_select",e),t.activeItems.indexOf(e)==-1&&t.activeItems.push(e)}removeActiveItem(e){var t=this.activeItems.indexOf(e);this.activeItems.splice(t,1),gt(e,"active")}clearActiveItems(){gt(this.activeItems,"active"),this.activeItems=[]}setActiveOption(e,t=!0){e!==this.activeOption&&(this.clearActiveOption(),e&&(this.activeOption=e,ae(this.focus_node,{"aria-activedescendant":e.getAttribute("id")}),ae(e,{"aria-selected":"true"}),$e(e,"active"),t&&this.scrollToOption(e)))}scrollToOption(e,t){if(!e)return;let i=this.dropdown_content,r=i.clientHeight,o=i.scrollTop||0,s=e.offsetHeight,a=e.getBoundingClientRect().top-i.getBoundingClientRect().top+o;a+s>r+o?this.scroll(a-r+s,t):a{e.setActiveItemClass(i)}))}inputState(){var e=this;e.control.contains(e.control_input)&&(ae(e.control_input,{placeholder:e.settings.placeholder}),e.activeItems.length>0||!e.isFocused&&e.settings.hidePlaceholder&&e.items.length>0?(e.setTextboxValue(),e.isInputHidden=!0):(e.settings.hidePlaceholder&&e.items.length>0&&ae(e.control_input,{placeholder:""}),e.isInputHidden=!1),e.wrapper.classList.toggle("input-hidden",e.isInputHidden))}inputValue(){return this.control_input.value.trim()}focus(){var e=this;if(e.isDisabled||e.isReadOnly)return;e.ignoreFocus=!0;let t=this.control_input.offsetWidth?this.control_input:this.focus_node;t.focus(),setTimeout(()=>{e.ignoreFocus=!1,t.getRootNode().activeElement===t&&this.onFocus()},0)}blur(){this.focus_node.blur(),this.onBlur()}getScoreFunction(e){return this.sifter.getScoreFunction(e,this.getSearchOptions())}getSearchOptions(){var e=this.settings,t=e.sortField;return typeof e.sortField=="string"&&(t=[{field:e.sortField}]),{fields:e.searchField,conjunction:e.searchConjunction,sort:t,nesting:e.nesting}}search(e){var t,i,r=this,o=this.getSearchOptions();if(r.settings.score&&(i=r.settings.score.call(r,e),typeof i!="function"))throw new Error('Tom Select "score" setting must be a function that returns a function');return r.isDropdownContentStale||e!==r.lastQuery?(r.lastQuery=e,/(.)\1{15,}/.test(e)&&(e=""),t=r.sifter.search(e,Object.assign(o,{score:i})),r.currentResults=t):t=Object.assign({},r.currentResults),r.settings.hideSelected&&(t.items=t.items.filter(s=>{let a=Fe(s.id);return!(a!==null&&r.items.indexOf(a)!==-1)})),t}refreshOptions(e=!0){var t,i,r,o,s,a,l,c,u,d;let p={},y=[];var m=this,g=m.inputValue();let _=g===m.lastQuery||g==""&&m.lastQuery==null;var C=m.search(g),O=null,w=m.settings.shouldOpen||!1,T=m.dropdown_content;_&&(O=m.activeOption,O&&(u=O.closest("[data-group]"))),o=C.items.length,typeof m.settings.maxOptions=="number"&&(o=Math.min(o,m.settings.maxOptions)),o>0&&(w=!0);let $=(I,L)=>{let W=p[I];if(W!==void 0){let Q=y[W];if(Q!==void 0)return[W,Q.fragment]}let G=document.createDocumentFragment();return W=y.length,y.push({fragment:G,order:L,optgroup:I}),[W,G]};for(t=0;t0&&(Q=Q.cloneNode(!0),ae(Q,{id:W.$id+"-clone-"+i,"aria-selected":null}),Q.classList.add("ts-cloned"),gt(Q,"active"),m.activeOption&&m.activeOption.dataset.value==L&&u&&u.dataset.group===s.toString()&&(O=Q)),ie.appendChild(Q),s!=""&&(p[s]=Ce)}}m.settings.lockOptgroupOrder&&y.sort((I,L)=>I.order-L.order),l=document.createDocumentFragment(),ve(y,I=>{let L=I.fragment,W=I.optgroup;if(!L||!L.children.length)return;let G=m.optgroups[W];if(G!==void 0){let Q=document.createDocumentFragment(),Z=m.render("optgroup_header",G);Nt(Q,Z),Nt(Q,L);let he=m.render("optgroup",{group:G,options:Q});Nt(l,he)}else Nt(l,L)}),T.innerHTML="",Nt(T,l),m.isDropdownContentStale=!1,m.settings.highlight&&(Za(T),C.query.length&&C.tokens.length&&ve(C.tokens,I=>{Ja(T,I.regex)}));var H=I=>{let L=m.render(I,{input:g});return L&&(w=!0,T.insertBefore(L,T.firstChild)),L};if(m.loading?H("loading"):m.settings.shouldLoad.call(m,g)?C.items.length===0&&H("no_results"):H("not_loading"),c=m.canCreate(g),c&&(d=H("option_create")),m.hasOptions=C.items.length>0||c,w){if(C.items.length>0){if(!O&&m.settings.mode==="single"&&m.items[0]!=null&&(O=m.getOption(m.items[0])),!T.contains(O)){let I=0;d&&!m.settings.addPrecedence&&(I=1),O=m.selectable()[I]}}else d&&(O=d);e&&!m.isOpen&&(m.open(),m.scrollToOption(O,"auto")),m.setActiveOption(O)}else m.clearActiveOption(),e&&m.isOpen&&m.close(!1)}selectable(){return this.dropdown_content.querySelectorAll("[data-selectable]")}addOption(e,t=!1){let i=this;if(Array.isArray(e))return i.addOptions(e,t),!1;let r=Fe(e[i.settings.valueField]);return r===null||i.options.hasOwnProperty(r)?(i.updateOption(e[i.settings.valueField],e),!1):(e.$order=e.$order||++i.order,e.$id=i.inputId+"-opt-"+e.$order,i.options[r]=e,i.isDropdownContentStale=!0,t&&(i.userOptions[r]=t,i.trigger("option_add",r,e)),r)}addOptions(e,t=!1){ve(e,i=>{this.addOption(i,t)})}registerOption(e){return this.addOption(e)}registerOptionGroup(e){var t=Fe(e[this.settings.optgroupValueField]);return t===null?!1:(e.$order=e.$order||++this.order,this.optgroups[t]=e,t)}addOptionGroup(e,t){var i;t[this.settings.optgroupValueField]=e,(i=this.registerOptionGroup(t))&&this.trigger("optgroup_add",i,t)}removeOptionGroup(e){this.optgroups.hasOwnProperty(e)&&(delete this.optgroups[e],this.clearCache(),this.trigger("optgroup_remove",e))}clearOptionGroups(){this.optgroups={},this.clearCache(),this.trigger("optgroup_clear")}updateOption(e,t){let i=this;var r,o;let s=Fe(e),a=Fe(t[i.settings.valueField]);if(s===null)return;let l=i.options[s];if(l==null)return;if(typeof a!="string")throw new Error("Value must be set in option data");let c=i.getOption(s),u=i.getItem(s);if(t.$order=t.$order||l.$order,delete i.options[s],i.uncacheValue(a),i.options[a]=t,c){if(i.dropdown_content.contains(c)){let d=i._render("option",t);li(c,d),i.activeOption===c&&i.setActiveOption(d)}c.remove()}u&&(o=i.items.indexOf(s),o!==-1&&i.items.splice(o,1,a),r=i._render("item",t),u.classList.contains("active")&&$e(r,"active"),li(u,r)),i.isDropdownContentStale=!0}removeOption(e,t){let i=this;e=si(e),i.uncacheValue(e),delete i.userOptions[e],delete i.options[e],i.isDropdownContentStale=!0,i.trigger("option_remove",e),i.removeItem(e,t)}clearOptions(e){let t=(e||this.clearFilter).bind(this);this.loadedSearches={},this.userOptions={},this.clearCache();let i={};ve(this.options,(r,o)=>{t(r,o)&&(i[o]=r)}),this.options=this.sifter.items=i,this.isDropdownContentStale=!0,this.trigger("option_clear")}clearFilter(e,t){return this.items.indexOf(t)>=0}getOption(e,t=!1){let i=Fe(e);if(i===null)return null;let r=this.options[i];if(r!=null){if(r.$div)return r.$div;if(t)return this._render("option",r)}return null}getAdjacent(e,t,i="option"){var r=this,o;if(!e)return null;i=="item"?o=r.controlChildren():o=r.dropdown_content.querySelectorAll("[data-selectable]");for(let s=0;s0?o[s+1]:o[s-1];return null}getItem(e){if(typeof e=="object")return e;var t=Fe(e);return t!==null?this.control.querySelector(`[data-value="${Fo(t)}"]`):null}addItems(e,t){var i=this,r=Array.isArray(e)?e:[e];r=r.filter(s=>i.items.indexOf(s)===-1);let o=r[r.length-1];r.forEach(s=>{i.isPending=s!==o,i.addItem(s,t)})}addItem(e,t){var i=t?[]:["change","dropdown_close"];Po(this,i,()=>{var r,o;let s=this,a=s.settings.mode,l=Fe(e);if(!(l&&s.items.indexOf(l)!==-1&&(a==="single"&&s.close(),a==="single"||!s.settings.duplicates))&&!(l===null||!s.options.hasOwnProperty(l))&&(a==="single"&&s.clear(t),!(a==="multi"&&s.isFull()))){if(r=s._render("item",s.options[l]),s.control.contains(r)&&(r=r.cloneNode(!0)),o=s.isFull(),s.items.splice(s.caretPos,0,l),s.insertAtCaret(r),s.isSetup){if(!s.isPending&&s.settings.hideSelected){let c=s.getOption(l),u=s.getAdjacent(c,1);u&&s.setActiveOption(u)}s.settings.clearAfterSelect&&s.setTextboxValue(),!s.isPending&&!s.settings.closeAfterSelect&&s.refreshOptions(s.isFocused&&a!=="single"),s.settings.closeAfterSelect!=!1&&s.isFull()?s.close():s.isPending||s.positionDropdown(),s.trigger("item_add",l,r),s.isPending||s.updateOriginalInput({silent:t})}(!s.isPending||!o&&s.isFull())&&(s.inputState(),s.refreshState())}})}removeItem(e=null,t){let i=this;if(e=i.getItem(e),!e)return;var r,o;let s=e.dataset.value;r=Vo(e),e.remove(),e.classList.contains("active")&&(o=i.activeItems.indexOf(e),i.activeItems.splice(o,1),gt(e,"active")),i.items.splice(r,1),i.isDropdownContentStale=!0,!i.settings.persist&&i.userOptions.hasOwnProperty(s)&&i.removeOption(s,t),r{}){arguments.length===3&&(t=arguments[2]),typeof t!="function"&&(t=()=>{});var i=this,r=i.caretPos,o;if(e=e||i.inputValue(),!i.canCreate(e))return Fe(e)&&this.options[e]&&i.addItem(e),t(),!1;i.lock();var s=!1,a=l=>{if(i.unlock(),!l||typeof l!="object")return t();var c=Fe(l[i.settings.valueField]);if(typeof c!="string")return t();i.setTextboxValue(),i.addOption(l,!0),i.setCaret(r),i.addItem(c),t(l),s=!0};return typeof i.settings.create=="function"?o=i.settings.create.call(this,e,a):o={[i.settings.labelField]:e,[i.settings.valueField]:e},s||a(o),!0}refreshItems(){var e=this;e.isDropdownContentStale=!0,e.isSetup&&e.addItems(e.items),e.updateOriginalInput(),e.refreshState()}refreshState(){let e=this;e.refreshValidityState();let t=e.isFull(),i=e.isLocked;e.wrapper.classList.toggle("rtl",e.rtl);let r=e.wrapper.classList;r.toggle("focus",e.isFocused),r.toggle("disabled",e.isDisabled),r.toggle("readonly",e.isReadOnly),r.toggle("required",e.isRequired),r.toggle("invalid",!e.isValid),r.toggle("locked",i),r.toggle("full",t),r.toggle("input-active",e.isFocused&&!e.isInputHidden),r.toggle("dropdown-active",e.isOpen),r.toggle("has-options",Qa(e.options)),r.toggle("has-items",e.items.length>0)}refreshValidityState(){var e=this;e.input.validity&&(e.isValid=e.input.validity.valid,e.isInvalid=!e.isValid)}isFull(){return this.settings.maxItems!==null&&this.items.length>=this.settings.maxItems}updateOriginalInput(e={}){let t=this;var i,r;let o=t.input.querySelector('option[value=""]');if(t.is_select_tag){let l=function(c,u,d){return c||(c=nt('")),c!=o&&t.input.append(c),s.push(c),(c!=o||a>0)&&(c.selected=!0),c},s=[],a=t.input.querySelectorAll("option:checked").length;t.input.querySelectorAll("option:checked").forEach(c=>{c.selected=!1}),t.items.length==0&&t.settings.mode=="single"?l(o,"",""):t.items.forEach(c=>{if(i=t.options[c],r=i[t.settings.labelField]||"",s.includes(i.$option)){let u=t.input.querySelector(`option[value="${Fo(c)}"]:not(:checked)`);l(u,c,r)}else i.$option=l(i.$option,c,r)})}else t.input.value=t.getValue();t.isSetup&&(e.silent||t.trigger("change",t.getValue()))}open(){var e=this;e.isLocked||e.isOpen||e.settings.mode==="multi"&&e.isFull()||(e.isOpen=!0,ae(e.focus_node,{"aria-expanded":"true"}),e.refreshState(),ai(e.dropdown,{visibility:"hidden",display:"block"}),e.positionDropdown(),ai(e.dropdown,{visibility:"visible",display:"block"}),e.focus(),e.trigger("dropdown_open",e.dropdown))}close(e=!0){var t=this,i=t.isOpen;e&&(t.setTextboxValue(),t.settings.mode==="single"&&t.items.length&&t.inputState()),t.isOpen=!1,ae(t.focus_node,{"aria-expanded":"false"}),ai(t.dropdown,{display:"none"}),t.settings.hideSelected&&t.clearActiveOption(),t.refreshState(),i&&t.trigger("dropdown_close",t.dropdown)}positionDropdown(){if(this.settings.dropdownParent==="body"){var e=this.control,t=e.getBoundingClientRect(),i=e.offsetHeight+t.top+window.scrollY,r=t.left+window.scrollX;ai(this.dropdown,{width:t.width+"px",top:i+"px",left:r+"px"})}}clear(e){var t=this;if(t.items.length){var i=t.controlChildren();ve(i,r=>{t.removeItem(r,!0)}),t.inputState(),e||t.updateOriginalInput(),t.trigger("clear")}}insertAtCaret(e){let t=this,i=t.caretPos,r=t.control;r.insertBefore(e,r.children[i]||null),t.setCaret(i+1)}deleteSelection(e){var t,i,r,o,s=this;t=e&&e.keyCode===8?-1:1,i=Ya(s.control_input);let a=[];if(s.activeItems.length)o=Bo(s.activeItems,t),r=Vo(o),t>0&&r++,ve(s.activeItems,l=>a.push(l));else if((s.isFocused||s.settings.mode==="single")&&s.items.length){let l=s.controlChildren(),c;t<0&&i.start===0&&i.length===0?c=l[s.caretPos-1]:t>0&&i.start===s.inputValue().length&&(c=l[s.caretPos]),c!==void 0&&a.push(c)}if(!s.shouldDelete(a,e))return!1;for(fe(e,!0),typeof r!="undefined"&&s.setCaret(r);a.length;)s.removeItem(a.pop());return s.inputState(),s.positionDropdown(),s.refreshOptions(!1),!0}shouldDelete(e,t){let i=e.map(r=>r.dataset.value);return!(!i.length||typeof this.settings.onDelete=="function"&&this.settings.onDelete.call(this,i,t)===!1)}advanceSelection(e,t){var i,r,o=this;o.rtl&&(e*=-1),!o.inputValue().length&&(Mt(ci,t)||Mt("shiftKey",t)?(i=o.getLastActive(e),i?i.classList.contains("active")?r=o.getAdjacent(i,e,"item"):r=i:e>0?r=o.control_input.nextElementSibling:r=o.control_input.previousElementSibling,r&&(r.classList.contains("active")&&o.removeActiveItem(i),o.setActiveItemClass(r))):o.moveCaret(e))}moveCaret(e){}getLastActive(e){let t=this.control.querySelector(".last-active");if(t)return t;var i=this.control.querySelectorAll(".active");if(i)return Bo(i,e)}setCaret(e){this.caretPos=this.items.length}controlChildren(){return Array.from(this.control.querySelectorAll("[data-ts-item]"))}lock(){this.setLocked(!0)}unlock(){this.setLocked(!1)}setLocked(e=this.isReadOnly||this.isDisabled){this.isLocked=e,this.refreshState()}disable(){this.setDisabled(!0),this.close()}enable(){this.setDisabled(!1)}setDisabled(e){this.focus_node.tabIndex=e?-1:this.tabIndex,this.isDisabled=e,this.input.disabled=e,this.control_input.disabled=e,this.setLocked()}setReadOnly(e){this.isReadOnly=e,this.input.readOnly=e,this.control_input.readOnly=e,this.setLocked()}destroy(){var e=this,t=e.revertSettings;e.trigger("destroy"),e.off(),e.wrapper.remove(),e.dropdown.remove(),e.input.innerHTML=t.innerHTML,e.input.tabIndex=t.tabIndex,gt(e.input,"tomselected","ts-hidden-accessible"),e._destroy(),delete e.input.tomselect}render(e,t){var i,r;let o=this;if(typeof this.settings.render[e]!="function"||(r=o.settings.render[e].call(this,t,ar),!r))return null;if(r=nt(r),e==="option"||e==="option_create"?t[o.settings.disabledField]?ae(r,{"aria-disabled":"true"}):ae(r,{"data-selectable":""}):e==="optgroup"&&(i=t.group[o.settings.optgroupValueField],ae(r,{"data-group":i}),t.group[o.settings.disabledField]&&ae(r,{"data-disabled":""})),e==="option"||e==="item"){let s=si(t[o.settings.valueField]);ae(r,{"data-value":s}),e==="item"?($e(r,o.settings.itemClass),ae(r,{"data-ts-item":""})):($e(r,o.settings.optionClass),ae(r,{role:"option",id:t.$id}),t.$div=r,o.options[s]=t)}return r}_render(e,t){let i=this.render(e,t);if(i==null)throw"HTMLElement expected";return i}clearCache(){ve(this.options,e=>{e.$div&&(e.$div.remove(),delete e.$div)})}uncacheValue(e){let t=this.getOption(e);t&&t.remove()}canCreate(e){return this.settings.create&&e.length>0&&this.settings.createFilter.call(this,e)}hook(e,t,i){var r=this,o=r[t];r[t]=function(){var s,a;return e==="after"&&(s=o.apply(r,arguments)),a=i.apply(r,arguments),e==="instead"?a:(e==="before"&&(s=o.apply(r,arguments)),s)}}};var xm=(n,e,t,i)=>{n.addEventListener(e,t,i)};function il(){xm(this.input,"change",()=>{this.sync()})}var Tm=n=>typeof n=="undefined"||n===null?null:Cm(n),Cm=n=>typeof n=="boolean"?n?"1":"0":n+"",rl=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Sm=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Am(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Am=n=>typeof n=="string"&&n.indexOf("<")>-1;function ol(n){var e=this,t=e.onOptionSelect;e.settings.hideSelected=!1;let i=Object.assign({className:"tomselect-checkbox",checkedClassNames:void 0,uncheckedClassNames:void 0},n);var r=function(a,l){l?(a.checked=!0,i.uncheckedClassNames&&a.classList.remove(...i.uncheckedClassNames),i.checkedClassNames&&a.classList.add(...i.checkedClassNames)):(a.checked=!1,i.checkedClassNames&&a.classList.remove(...i.checkedClassNames),i.uncheckedClassNames&&a.classList.add(...i.uncheckedClassNames))},o=function(a){setTimeout(()=>{var l=a.querySelector("input."+i.className);l instanceof HTMLInputElement&&r(l,a.classList.contains("selected"))},1)};e.hook("after","setupTemplates",()=>{var s=e.settings.render.option;e.settings.render.option=(a,l)=>{var c=Sm(s.call(e,a,l)),u=document.createElement("input");i.className&&u.classList.add(i.className),u.addEventListener("click",function(p){rl(p)}),u.type="checkbox";let d=Tm(a[e.settings.valueField]);return r(u,!!(d&&e.items.indexOf(d)>-1)),c.prepend(u),c}}),e.on("item_remove",s=>{var a=e.getOption(s);a&&(a.classList.remove("selected"),o(a))}),e.on("item_add",s=>{var a=e.getOption(s);a&&o(a)}),e.hook("instead","onOptionSelect",(s,a)=>{if(a.classList.contains("selected")){a.classList.remove("selected"),e.removeItem(a.dataset.value),e.refreshOptions(),rl(s,!0);return}t.call(e,s,a),o(a)})}var Dm=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Om(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Om=n=>typeof n=="string"&&n.indexOf("<")>-1;function sl(n){let e=this,t=Object.assign({className:"clear-button",title:"Clear All",role:"button",tabindex:0,html:i=>`
×
`},n);e.on("initialize",()=>{var i=Dm(t.html(t));i.addEventListener("click",r=>{e.isLocked||(e.clear(),e.settings.mode==="single"&&e.settings.allowEmptyOption&&e.addItem(""),e.refreshOptions(!1),r.preventDefault(),r.stopPropagation())}),e.control.appendChild(i)})}var Lm=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},An=(n,e,t,i)=>{n.addEventListener(e,t,i)},Mm=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},Nm=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(km(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},km=n=>typeof n=="string"&&n.indexOf("<")>-1,Hm=(n,e)=>{Mm(e,(t,i)=>{t==null?n.removeAttribute(i):n.setAttribute(i,""+t)})},Im=(n,e)=>{var t;(t=n.parentNode)==null||t.insertBefore(e,n.nextSibling)},Rm=(n,e)=>{var t;(t=n.parentNode)==null||t.insertBefore(e,n)},Pm=(n,e)=>{do{var t;if(e=(t=e)==null?void 0:t.previousElementSibling,n==e)return!0}while(e&&e.previousElementSibling);return!1};function al(){var n=this;if(n.settings.mode!=="multi")return;var e=n.lock,t=n.unlock;let i=!0,r;n.hook("after","setupTemplates",()=>{var o=n.settings.render.item;n.settings.render.item=(s,a)=>{let l=Nm(o.call(n,s,a));Hm(l,{draggable:"true"});let c=g=>{i||Lm(g),g.stopPropagation()},u=g=>{r=l,setTimeout(()=>{l.classList.add("ts-dragging")},0)},d=g=>{g.preventDefault(),l.classList.add("ts-drag-over"),y(l,r)},p=()=>{l.classList.remove("ts-drag-over")},y=(g,_)=>{_!==void 0&&(Pm(_,l)?Im(g,_):Rm(g,_))},m=()=>{var g;document.querySelectorAll(".ts-drag-over").forEach(C=>C.classList.remove("ts-drag-over")),(g=r)==null||g.classList.remove("ts-dragging"),r=void 0;var _=[];n.control.querySelectorAll("[data-value]").forEach(C=>{if(C.dataset.value){let O=C.dataset.value;O&&_.push(O)}}),n.setValue(_)};return An(l,"mousedown",c),An(l,"dragstart",u),An(l,"dragenter",d),An(l,"dragover",d),An(l,"dragleave",p),An(l,"dragend",m),l}}),n.hook("instead","lock",()=>(i=!1,e.call(n))),n.hook("instead","unlock",()=>(i=!0,t.call(n)))}var Fm=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},$m=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Bm(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Bm=n=>typeof n=="string"&&n.indexOf("<")>-1;function ll(n){let e=this,t=Object.assign({title:"Untitled",headerClass:"dropdown-header",titleRowClass:"dropdown-header-title",labelClass:"dropdown-header-label",closeClass:"dropdown-header-close",html:i=>'
'+i.title+'×
'},n);e.on("initialize",()=>{var i=$m(t.html(t)),r=i.querySelector("."+t.closeClass);r&&r.addEventListener("click",o=>{Fm(o,!0),e.close()}),e.dropdown.insertBefore(i,e.dropdown.firstChild)})}var Vm=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},zm=(n,...e)=>{var t=jm(e);n=Wm(n),n.map(i=>{t.map(r=>{i.classList.remove(r)})})},jm=n=>{var e=[];return Vm(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Wm=n=>(Array.isArray(n)||(n=[n]),n),qm=(n,e)=>{if(!n)return-1;e=e||n.nodeName;for(var t=0;n=n.previousElementSibling;)n.matches(e)&&t++;return t};function cl(){var n=this;n.hook("instead","setCaret",e=>{n.settings.mode==="single"||!n.control.contains(n.control_input)?e=n.items.length:(e=Math.max(0,Math.min(n.items.length,e)),e!=n.caretPos&&!n.isPending&&n.controlChildren().forEach((t,i)=>{i{if(!n.isFocused)return;let t=n.getLastActive(e);if(t){let i=qm(t);n.setCaret(e>0?i+1:i),n.setActiveItem(),zm(t,"last-active")}else n.setCaret(n.caretPos+e)})}var Um=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Ym=(n,e,t,i)=>{n.addEventListener(e,t,i)},Gm=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},ul=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Km(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Km=n=>typeof n=="string"&&n.indexOf("<")>-1,Xm=(n,...e)=>{var t=Qm(e);n=Jm(n),n.map(i=>{t.map(r=>{i.classList.add(r)})})},Qm=n=>{var e=[];return Gm(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Jm=n=>(Array.isArray(n)||(n=[n]),n);function dl(){let n=this;n.settings.shouldOpen=!0,n.hook("before","setup",()=>{var e;n.focus_node=n.control,Xm(n.control_input,"dropdown-input");let t=ul('