Compare commits

..

6 Commits

Author SHA1 Message Date
Jeremy Stretch
e5f6fc55ea Fixes #21181: Handle AuthenticationFailed exception on /media endpoint 2026-01-19 10:17:50 -05:00
Martin Hauser
6b7b38ee0a fix(users): Refactor object permission query logic
Simplifies the `OBJECTPERMISSION_OBJECT_TYPES` definition by adjusting
query filters and introducing new conditions for specific app labels
and models.

Fixes #21051
2026-01-19 09:30:36 -05:00
matthew-242
c8f17e06a2 Add support to filter on cached relations _location, _region, _site and _site_group to ScopedFilterMixin (#21162) 2026-01-19 09:09:03 -05:00
Jeremy Stretch
edace6aff4 Fixes #21166: Fix support for filtering on unsigned 32-bit integer values in GraphQL API (#21186)
* Fixes #21166: Fix support for filtering on unsigned 32-bit integer values in GraphQL API

* tunnel_id should also use BigIntegerLookup
2026-01-19 08:54:39 -05:00
github-actions
586bc132b6 Update source translation strings
Some checks failed
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Lock threads / lock (push) Has been cancelled
Close stale issues/PRs / stale (push) Has been cancelled
Close incomplete issues / stale (push) Has been cancelled
Update translation strings / makemessages (push) Has been cancelled
2026-01-17 05:02:55 +00:00
Arthur Hanson
52a2b934a0 Fixes #21160: Fix performance issue rendering FilterSet forms w/ large choicesets (#21200)
Some checks failed
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
CI / build (20.x, 3.13) (push) Has been cancelled
CI / build (20.x, 3.14) (push) Has been cancelled
Lock threads / lock (push) Has been cancelled
Close stale issues/PRs / stale (push) Has been cancelled
Close incomplete issues / stale (push) Has been cancelled
Update translation strings / makemessages (push) Has been cancelled
2026-01-16 16:34:12 -06:00
14 changed files with 201 additions and 201 deletions

View File

@@ -29,15 +29,6 @@ class DCIMConfig(AppConfig):
denormalized.register(CableTermination, '_location', {
'_site': 'site',
})
denormalized.register(Device, 'virtual_chassis', {
'_virtual_chassis_name': 'name',
})
denormalized.register(Device, 'primary_ip4', {
'_primary_ip4_address': 'address',
})
denormalized.register(Device, 'primary_ip6', {
'_primary_ip6_address': 'address',
})
# Register counters
connect_counters(Device, DeviceType, ModuleType, RackType, VirtualChassis)

View File

@@ -1331,14 +1331,13 @@ class DeviceFilterSet(
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(virtual_chassis__name__icontains=value) |
Q(serial__icontains=value.strip()) |
Q(asset_tag__icontains=value.strip()) |
Q(description__icontains=value.strip()) |
Q(comments__icontains=value) |
# Denormalized fields
Q(_virtual_chassis_name__icontains=value) |
Q(_primary_ip4_address__startswith=value) |
Q(_primary_ip6_address__startswith=value)
Q(primary_ip4__address__startswith=value) |
Q(primary_ip6__address__startswith=value)
).distinct()
def _has_primary_ip(self, queryset, name, value):

View File

@@ -13,6 +13,7 @@ if TYPE_CHECKING:
from netbox.graphql.filter_lookups import IntegerLookup
from extras.graphql.filters import ConfigTemplateFilter
from ipam.graphql.filters import VLANFilter, VLANTranslationPolicyFilter
from dcim.graphql.filters import LocationFilter, RegionFilter, SiteFilter, SiteGroupFilter
from .filters import *
__all__ = (
@@ -35,6 +36,20 @@ class ScopedFilterMixin:
)
scope_id: ID | None = strawberry_django.filter_field()
# Cached relations
_location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field(name='location')
)
_region: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field(name='region')
)
_site_group: Annotated['SiteGroupFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field(name='site_group')
)
_site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field(name='site')
)
@dataclass
class ComponentModelFilterMixin:

View File

@@ -224,7 +224,7 @@ class ConsoleServerPortTemplateType(ModularComponentTemplateType):
@strawberry_django.type(
models.Device,
exclude=['_virtual_chassis_name', '_primary_ip4_address', '_primary_ip6_address'],
fields='__all__',
filters=DeviceFilter,
pagination=True
)

View File

@@ -1,46 +0,0 @@
from django.db import migrations, models
from django.db.models import Q
import ipam.fields
def backfill_denormalized_fields(apps, schema_editor):
Device = apps.get_model('dcim', 'Device')
# TODO: Optimize for bulk operations
for device in Device.objects.filter(
Q(virtual_chassis__isnull=False) | Q(primary_ip4__isnull=False) | Q(primary_ip6__isnull=False)
):
device._virtual_chassis_name = device.virtual_chassis.name if device.virtual_chassis else ''
device._primary_ip4_address = device.primary_ip4.address if device.primary_ip4 else None
device._primary_ip6_address = device.primary_ip6.address if device.primary_ip6 else None
device.save()
class Migration(migrations.Migration):
dependencies = [
('dcim', '0225_gfk_indexes'),
]
operations = [
migrations.AddField(
model_name='device',
name='_primary_ip4_address',
field=ipam.fields.IPAddressField(blank=True, null=True),
),
migrations.AddField(
model_name='device',
name='_primary_ip6_address',
field=ipam.fields.IPAddressField(blank=True, null=True),
),
migrations.AddField(
model_name='device',
name='_virtual_chassis_name',
field=models.CharField(blank=True),
),
migrations.RunPython(
code=backfill_denormalized_fields,
reverse_code=migrations.RunPython.noop
),
]

View File

@@ -21,7 +21,6 @@ from dcim.fields import MACAddressField
from dcim.utils import create_port_mappings, update_interface_bridges
from extras.models import ConfigContextModel, CustomField
from extras.querysets import ConfigContextModelQuerySet
from ipam.fields import IPAddressField
from netbox.choices import ColorChoices
from netbox.config import ConfigItem
from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel
@@ -674,19 +673,6 @@ class Device(
related_query_name='device',
)
# Denormalized fields
_virtual_chassis_name = models.CharField(
blank=True,
)
_primary_ip4_address = IPAddressField(
blank=True,
null=True,
)
_primary_ip6_address = IPAddressField(
blank=True,
null=True,
)
# Counter fields
console_port_count = CounterCacheField(
to_model='dcim.ConsolePort',

View File

@@ -424,40 +424,34 @@ class IPAddressForm(TenancyForm, PrimaryModelForm):
def save(self, *args, **kwargs):
ipaddress = super().save(*args, **kwargs)
# Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
interface = self.instance.assigned_object
if type(interface) in (Interface, VMInterface):
parent = interface.parent_object
parent.snapshot()
update_fields = []
# Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
if self.cleaned_data['primary_for_parent']:
if ipaddress.address.version == 4:
parent.primary_ip4 = ipaddress
update_fields.append('primary_ip4')
else:
parent.primary_ip6 = ipaddress
update_fields.append('primary_ip6')
parent.save()
elif ipaddress.address.version == 4 and parent.primary_ip4 == ipaddress:
parent.primary_ip4 = None
update_fields.append('primary_ip4')
parent.save()
elif ipaddress.address.version == 6 and parent.primary_ip6 == ipaddress:
parent.primary_ip6 = None
update_fields.append('primary_ip6')
parent.save()
# Assign/clear this IPAddress as the OOB for the associated Device
if type(interface) is Interface:
if self.cleaned_data['oob_for_parent']:
parent.oob_ip = ipaddress
update_fields.append('oob_ip')
elif parent.oob_ip == ipaddress:
parent.oob_ip = None
update_fields.append('oob_ip')
# Save the parent object if appropriate. Update only the relevant fields to avoid conflicts with e.g.
# denormalized data on the parent object.
if update_fields:
parent.save(update_fields=update_fields)
# Assign/clear this IPAddress as the OOB for the associated Device
if type(interface) is Interface:
parent = interface.parent_object
parent.snapshot()
if self.cleaned_data['oob_for_parent']:
parent.oob_ip = ipaddress
parent.save()
elif parent.oob_ip == ipaddress:
parent.oob_ip = None
parent.save()
return ipaddress

View File

@@ -20,7 +20,7 @@ from tenancy.graphql.filter_mixins import ContactFilterMixin, TenancyFilterMixin
from virtualization.models import VMInterface
if TYPE_CHECKING:
from netbox.graphql.filter_lookups import IntegerLookup, IntegerRangeArrayLookup
from netbox.graphql.filter_lookups import BigIntegerLookup, IntegerLookup, IntegerRangeArrayLookup
from circuits.graphql.filters import ProviderFilter
from core.graphql.filters import ContentTypeFilter
from dcim.graphql.filters import SiteFilter
@@ -53,7 +53,7 @@ __all__ = (
class ASNFilter(TenancyFilterMixin, PrimaryModelFilter):
rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
rir_id: ID | None = strawberry_django.filter_field()
asn: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
asn: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
sites: (
@@ -70,10 +70,10 @@ class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter):
slug: FilterLookup[str] | 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['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
start: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
end: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
end: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)

View File

@@ -19,8 +19,11 @@ from strawberry_django import (
process_filters,
)
from netbox.graphql.scalars import BigInt
__all__ = (
'ArrayLookup',
'BigIntegerLookup',
'FloatArrayLookup',
'FloatLookup',
'IntegerArrayLookup',
@@ -78,6 +81,29 @@ class IntegerLookup:
return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix)
@strawberry.input(one_of=True, description='Lookup for BigInteger fields. Only one of the lookup fields can be set.')
class BigIntegerLookup:
filter_lookup: FilterLookup[BigInt] | None = strawberry_django.filter_field()
range_lookup: RangeLookup[BigInt] | None = strawberry_django.filter_field()
comparison_lookup: ComparisonFilterLookup[BigInt] | None = strawberry_django.filter_field()
def get_filter(self):
for field in self.__strawberry_definition__.fields:
value = getattr(self, field.name, None)
if value is not strawberry.UNSET:
return value
return None
@strawberry_django.filter_field
def filter(self, info: Info, queryset: QuerySet, prefix: DirectiveValue[str] = '') -> Tuple[QuerySet, Q]:
filters = self.get_filter()
if not filters:
return queryset, Q()
return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix)
@strawberry.input(one_of=True, description='Lookup for Float fields. Only one of the lookup fields can be set.')
class FloatLookup:
filter_lookup: FilterLookup[float] | None = strawberry_django.filter_field()

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-16 05:04+0000\n"
"POT-Creation-Date: 2026-01-17 05:02+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -806,7 +806,7 @@ msgstr ""
#: netbox/circuits/forms/model_forms.py:335
#: netbox/dcim/forms/model_forms.py:145 netbox/dcim/forms/model_forms.py:186
#: netbox/dcim/forms/model_forms.py:273 netbox/dcim/forms/model_forms.py:330
#: netbox/dcim/forms/model_forms.py:863 netbox/dcim/forms/model_forms.py:1917
#: netbox/dcim/forms/model_forms.py:863 netbox/dcim/forms/model_forms.py:1877
#: netbox/ipam/forms/bulk_edit.py:380 netbox/ipam/forms/model_forms.py:67
#: netbox/ipam/forms/model_forms.py:84 netbox/ipam/forms/model_forms.py:115
#: netbox/ipam/forms/model_forms.py:136 netbox/ipam/forms/model_forms.py:160
@@ -1012,8 +1012,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:262 netbox/dcim/forms/bulk_import.py:1193
#: netbox/dcim/forms/filtersets.py:399 netbox/dcim/forms/filtersets.py:865
#: netbox/dcim/forms/filtersets.py:1872 netbox/dcim/forms/filtersets.py:1912
#: netbox/dcim/forms/model_forms.py:255 netbox/dcim/forms/model_forms.py:1235
#: netbox/dcim/forms/model_forms.py:1737 netbox/dcim/forms/object_import.py:182
#: netbox/dcim/forms/model_forms.py:255 netbox/dcim/forms/model_forms.py:1214
#: netbox/dcim/forms/model_forms.py:1697 netbox/dcim/forms/object_import.py:182
#: netbox/dcim/tables/devices.py:171 netbox/dcim/tables/devices.py:857
#: netbox/dcim/tables/devices.py:983 netbox/dcim/tables/devicetypes.py:317
#: netbox/dcim/tables/racks.py:117 netbox/extras/filtersets.py:708
@@ -1132,9 +1132,9 @@ msgstr ""
#: netbox/circuits/forms/bulk_import.py:258
#: netbox/circuits/forms/model_forms.py:358
#: netbox/circuits/tables/virtual_circuits.py:108
#: netbox/dcim/forms/bulk_import.py:1324 netbox/dcim/forms/model_forms.py:1309
#: netbox/dcim/forms/model_forms.py:1578 netbox/dcim/forms/model_forms.py:1778
#: netbox/dcim/forms/model_forms.py:1813 netbox/dcim/forms/model_forms.py:1938
#: netbox/dcim/forms/bulk_import.py:1324 netbox/dcim/forms/model_forms.py:1288
#: netbox/dcim/forms/model_forms.py:1557 netbox/dcim/forms/model_forms.py:1738
#: netbox/dcim/forms/model_forms.py:1773 netbox/dcim/forms/model_forms.py:1898
#: netbox/dcim/tables/connections.py:65 netbox/dcim/tables/devices.py:1150
#: netbox/ipam/forms/bulk_import.py:319 netbox/ipam/forms/model_forms.py:280
#: netbox/ipam/forms/model_forms.py:289 netbox/ipam/tables/fhrp.py:61
@@ -1852,8 +1852,8 @@ msgstr ""
#: netbox/dcim/forms/filtersets.py:1866 netbox/dcim/forms/filtersets.py:1907
#: netbox/dcim/forms/filtersets.py:2000 netbox/dcim/forms/filtersets.py:2024
#: netbox/dcim/forms/filtersets.py:2048 netbox/dcim/forms/model_forms.py:728
#: netbox/dcim/forms/model_forms.py:943 netbox/dcim/forms/model_forms.py:1376
#: netbox/dcim/forms/model_forms.py:1889 netbox/dcim/forms/model_forms.py:1962
#: netbox/dcim/forms/model_forms.py:943 netbox/dcim/forms/model_forms.py:1355
#: netbox/dcim/forms/model_forms.py:1849 netbox/dcim/forms/model_forms.py:1922
#: netbox/dcim/forms/object_create.py:205 netbox/dcim/tables/connections.py:22
#: netbox/dcim/tables/connections.py:41 netbox/dcim/tables/connections.py:60
#: netbox/dcim/tables/devices.py:291 netbox/dcim/tables/devices.py:386
@@ -3084,8 +3084,8 @@ msgstr ""
#: netbox/dcim/forms/filtersets.py:768 netbox/dcim/forms/filtersets.py:783
#: netbox/dcim/forms/model_forms.py:81 netbox/dcim/forms/model_forms.py:99
#: netbox/dcim/forms/model_forms.py:176 netbox/dcim/forms/model_forms.py:502
#: netbox/dcim/forms/model_forms.py:523 netbox/dcim/forms/model_forms.py:1227
#: netbox/dcim/forms/model_forms.py:1729 netbox/dcim/forms/object_import.py:177
#: netbox/dcim/forms/model_forms.py:523 netbox/dcim/forms/model_forms.py:1206
#: netbox/dcim/forms/model_forms.py:1689 netbox/dcim/forms/object_import.py:177
#: netbox/dcim/tables/devices.py:702 netbox/dcim/tables/devices.py:916
#: netbox/dcim/tables/devices.py:1003 netbox/dcim/tables/devices.py:1156
#: netbox/ipam/forms/bulk_import.py:578 netbox/ipam/forms/model_forms.py:755
@@ -3217,7 +3217,7 @@ msgstr ""
#: netbox/dcim/choices.py:885 netbox/dcim/choices.py:1351
#: netbox/dcim/forms/bulk_edit.py:1543 netbox/dcim/forms/filtersets.py:1553
#: netbox/dcim/forms/filtersets.py:1678 netbox/dcim/forms/model_forms.py:1105
#: netbox/dcim/forms/model_forms.py:1590 netbox/netbox/navigation/menu.py:147
#: netbox/dcim/forms/model_forms.py:1569 netbox/netbox/navigation/menu.py:147
#: netbox/netbox/navigation/menu.py:151
#: netbox/templates/dcim/interface.html:280
msgid "Wireless"
@@ -3835,7 +3835,7 @@ msgstr ""
#: netbox/dcim/filtersets.py:1242 netbox/dcim/forms/filtersets.py:906
#: netbox/dcim/forms/filtersets.py:1609 netbox/dcim/forms/filtersets.py:1947
#: netbox/dcim/forms/model_forms.py:1935 netbox/dcim/models/devices.py:1307
#: netbox/dcim/forms/model_forms.py:1895 netbox/dcim/models/devices.py:1307
#: netbox/dcim/models/devices.py:1330 netbox/virtualization/filtersets.py:211
#: netbox/virtualization/filtersets.py:284
#: netbox/virtualization/forms/filtersets.py:187
@@ -4001,7 +4001,7 @@ msgstr ""
#: netbox/dcim/filtersets.py:1942 netbox/dcim/forms/bulk_edit.py:1509
#: netbox/dcim/forms/bulk_import.py:1027 netbox/dcim/forms/filtersets.py:1662
#: netbox/dcim/forms/model_forms.py:1556
#: netbox/dcim/forms/model_forms.py:1535
#: netbox/dcim/models/device_components.py:866
#: netbox/dcim/tables/devices.py:660 netbox/ipam/filtersets.py:345
#: netbox/ipam/filtersets.py:356 netbox/ipam/filtersets.py:489
@@ -4060,7 +4060,7 @@ msgid "VLAN Translation Policy (ID)"
msgstr ""
#: netbox/dcim/filtersets.py:1970 netbox/dcim/forms/filtersets.py:1633
#: netbox/dcim/forms/model_forms.py:1573
#: netbox/dcim/forms/model_forms.py:1552
#: netbox/dcim/models/device_components.py:668
#: netbox/ipam/forms/filtersets.py:518 netbox/ipam/forms/model_forms.py:700
#: netbox/templates/ipam/vlantranslationpolicy.html:11
@@ -4114,14 +4114,14 @@ msgstr ""
msgid "Primary MAC address (ID)"
msgstr ""
#: netbox/dcim/filtersets.py:2057 netbox/dcim/forms/model_forms.py:1560
#: netbox/dcim/filtersets.py:2057 netbox/dcim/forms/model_forms.py:1539
#: netbox/virtualization/filtersets.py:295
#: netbox/virtualization/forms/model_forms.py:302
msgid "Primary MAC address"
msgstr ""
#: netbox/dcim/filtersets.py:2079 netbox/dcim/filtersets.py:2091
#: netbox/dcim/forms/filtersets.py:1569 netbox/dcim/forms/model_forms.py:1915
#: netbox/dcim/forms/filtersets.py:1569 netbox/dcim/forms/model_forms.py:1875
#: netbox/templates/dcim/virtualdevicecontext.html:15
msgid "Virtual Device Context"
msgstr ""
@@ -4243,7 +4243,7 @@ msgstr ""
#: netbox/dcim/forms/filtersets.py:1917 netbox/dcim/forms/model_forms.py:211
#: netbox/dcim/forms/model_forms.py:342 netbox/dcim/forms/model_forms.py:354
#: netbox/dcim/forms/model_forms.py:424 netbox/dcim/forms/model_forms.py:528
#: netbox/dcim/forms/model_forms.py:1240 netbox/dcim/forms/model_forms.py:1742
#: netbox/dcim/forms/model_forms.py:1219 netbox/dcim/forms/model_forms.py:1702
#: netbox/dcim/forms/object_import.py:188 netbox/dcim/tables/devices.py:99
#: netbox/dcim/tables/devices.py:174 netbox/dcim/tables/devices.py:986
#: netbox/dcim/tables/devicetypes.py:86 netbox/dcim/tables/devicetypes.py:321
@@ -4421,7 +4421,7 @@ msgstr ""
#: netbox/dcim/forms/filtersets.py:524 netbox/dcim/forms/filtersets.py:667
#: netbox/dcim/forms/filtersets.py:809 netbox/dcim/forms/filtersets.py:1028
#: netbox/dcim/forms/model_forms.py:432 netbox/dcim/forms/model_forms.py:767
#: netbox/dcim/forms/model_forms.py:1810
#: netbox/dcim/forms/model_forms.py:1770
#: netbox/templates/dcim/device_edit.html:22
msgid "Hardware"
msgstr ""
@@ -4447,8 +4447,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:531 netbox/dcim/forms/model_forms.py:373
#: netbox/dcim/forms/model_forms.py:1002 netbox/dcim/forms/model_forms.py:1044
#: netbox/dcim/forms/model_forms.py:1071 netbox/dcim/forms/model_forms.py:1099
#: netbox/dcim/forms/model_forms.py:1120 netbox/dcim/forms/model_forms.py:1181
#: netbox/dcim/forms/model_forms.py:1199 netbox/dcim/forms/object_create.py:117
#: netbox/dcim/forms/model_forms.py:1120 netbox/dcim/forms/model_forms.py:1160
#: netbox/dcim/forms/model_forms.py:1178 netbox/dcim/forms/object_create.py:117
#: netbox/dcim/tables/devicetypes.py:83 netbox/templates/dcim/devicebay.html:52
#: netbox/templates/dcim/module.html:61
msgid "Device Type"
@@ -4477,7 +4477,7 @@ msgstr ""
#: netbox/dcim/forms/model_forms.py:431 netbox/dcim/forms/model_forms.py:1003
#: netbox/dcim/forms/model_forms.py:1045 netbox/dcim/forms/model_forms.py:1072
#: netbox/dcim/forms/model_forms.py:1100 netbox/dcim/forms/model_forms.py:1121
#: netbox/dcim/forms/model_forms.py:1182 netbox/dcim/forms/model_forms.py:1200
#: netbox/dcim/forms/model_forms.py:1161 netbox/dcim/forms/model_forms.py:1179
#: netbox/dcim/forms/object_create.py:118 netbox/dcim/tables/modules.py:51
#: netbox/dcim/tables/modules.py:94 netbox/templates/dcim/module.html:92
#: netbox/templates/dcim/modulebay.html:66
@@ -4661,8 +4661,8 @@ msgid "Allocated power draw (watts)"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1058 netbox/dcim/forms/bulk_import.py:885
#: netbox/dcim/forms/model_forms.py:1060 netbox/dcim/forms/model_forms.py:1446
#: netbox/dcim/forms/model_forms.py:1794 netbox/dcim/forms/object_import.py:56
#: netbox/dcim/forms/model_forms.py:1060 netbox/dcim/forms/model_forms.py:1425
#: netbox/dcim/forms/model_forms.py:1754 netbox/dcim/forms/object_import.py:56
msgid "Power port"
msgstr ""
@@ -4697,7 +4697,7 @@ msgid "Wireless role"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1268 netbox/dcim/forms/model_forms.py:766
#: netbox/dcim/forms/model_forms.py:1391 netbox/dcim/tables/devices.py:328
#: netbox/dcim/forms/model_forms.py:1370 netbox/dcim/tables/devices.py:328
#: netbox/templates/dcim/consoleport.html:24
#: netbox/templates/dcim/consoleserverport.html:24
#: netbox/templates/dcim/frontport.html:24
@@ -4715,7 +4715,7 @@ msgstr ""
msgid "LAG"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1415 netbox/dcim/forms/model_forms.py:1473
#: netbox/dcim/forms/bulk_edit.py:1415 netbox/dcim/forms/model_forms.py:1452
msgid "Virtual device contexts"
msgstr ""
@@ -4744,7 +4744,7 @@ msgid "Mode"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1458 netbox/dcim/forms/bulk_import.py:993
#: netbox/dcim/forms/model_forms.py:1522 netbox/ipam/forms/bulk_import.py:173
#: netbox/dcim/forms/model_forms.py:1501 netbox/ipam/forms/bulk_import.py:173
#: netbox/ipam/forms/filtersets.py:568 netbox/ipam/models/vlans.py:93
#: netbox/virtualization/forms/bulk_edit.py:205
#: netbox/virtualization/forms/bulk_import.py:185
@@ -4753,7 +4753,7 @@ msgid "VLAN group"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1467 netbox/dcim/forms/bulk_import.py:1000
#: netbox/dcim/forms/model_forms.py:1528 netbox/dcim/tables/devices.py:605
#: netbox/dcim/forms/model_forms.py:1507 netbox/dcim/tables/devices.py:605
#: netbox/virtualization/forms/bulk_edit.py:213
#: netbox/virtualization/forms/bulk_import.py:192
#: netbox/virtualization/forms/model_forms.py:331
@@ -4761,7 +4761,7 @@ msgid "Untagged VLAN"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1476 netbox/dcim/forms/bulk_import.py:1007
#: netbox/dcim/forms/model_forms.py:1537 netbox/dcim/tables/devices.py:611
#: netbox/dcim/forms/model_forms.py:1516 netbox/dcim/tables/devices.py:611
#: netbox/virtualization/forms/bulk_edit.py:221
#: netbox/virtualization/forms/bulk_import.py:199
#: netbox/virtualization/forms/model_forms.py:340
@@ -4777,18 +4777,18 @@ msgid "Remove tagged VLANs"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1499 netbox/dcim/forms/bulk_import.py:1020
#: netbox/dcim/forms/model_forms.py:1546
#: netbox/dcim/forms/model_forms.py:1525
#: netbox/virtualization/forms/bulk_import.py:212
#: netbox/virtualization/forms/model_forms.py:349
msgid "Q-in-Q Service VLAN"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1514 netbox/dcim/forms/model_forms.py:1509
#: netbox/dcim/forms/bulk_edit.py:1514 netbox/dcim/forms/model_forms.py:1488
#: netbox/wireless/forms/filtersets.py:26
msgid "Wireless LAN group"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1519 netbox/dcim/forms/model_forms.py:1514
#: netbox/dcim/forms/bulk_edit.py:1519 netbox/dcim/forms/model_forms.py:1493
#: netbox/dcim/tables/devices.py:653 netbox/netbox/navigation/menu.py:153
#: netbox/templates/dcim/interface.html:350
#: netbox/wireless/tables/wirelesslan.py:20
@@ -4796,7 +4796,7 @@ msgid "Wireless LANs"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1528 netbox/dcim/forms/filtersets.py:1550
#: netbox/dcim/forms/model_forms.py:1580 netbox/ipam/forms/bulk_edit.py:224
#: netbox/dcim/forms/model_forms.py:1559 netbox/ipam/forms/bulk_edit.py:224
#: netbox/ipam/forms/bulk_edit.py:310 netbox/ipam/forms/filtersets.py:184
#: netbox/netbox/navigation/menu.py:109
#: netbox/templates/dcim/interface.html:141
@@ -4808,18 +4808,18 @@ msgid "Addressing"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1529 netbox/dcim/forms/filtersets.py:808
#: netbox/dcim/forms/model_forms.py:1581
#: netbox/dcim/forms/model_forms.py:1560
#: netbox/virtualization/forms/model_forms.py:370
msgid "Operation"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1530 netbox/dcim/forms/filtersets.py:1551
#: netbox/dcim/forms/filtersets.py:1677 netbox/dcim/forms/model_forms.py:1104
#: netbox/dcim/forms/model_forms.py:1583
#: netbox/dcim/forms/model_forms.py:1562
msgid "PoE"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1531 netbox/dcim/forms/model_forms.py:1582
#: netbox/dcim/forms/bulk_edit.py:1531 netbox/dcim/forms/model_forms.py:1561
#: netbox/templates/dcim/interface.html:105
#: netbox/virtualization/forms/bulk_edit.py:237
#: netbox/virtualization/forms/model_forms.py:371
@@ -4827,7 +4827,7 @@ msgid "Related Interfaces"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1533 netbox/dcim/forms/filtersets.py:1552
#: netbox/dcim/forms/model_forms.py:1586
#: netbox/dcim/forms/model_forms.py:1565
#: netbox/virtualization/forms/bulk_edit.py:240
#: netbox/virtualization/forms/filtersets.py:215
#: netbox/virtualization/forms/model_forms.py:374
@@ -5106,13 +5106,13 @@ msgstr ""
msgid "Electrical phase (for three-phase circuits)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:939 netbox/dcim/forms/model_forms.py:1484
#: netbox/dcim/forms/bulk_import.py:939 netbox/dcim/forms/model_forms.py:1463
#: netbox/virtualization/forms/bulk_import.py:169
#: netbox/virtualization/forms/model_forms.py:310
msgid "Parent interface"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:946 netbox/dcim/forms/model_forms.py:1492
#: netbox/dcim/forms/bulk_import.py:946 netbox/dcim/forms/model_forms.py:1471
#: netbox/virtualization/forms/bulk_import.py:176
#: netbox/virtualization/forms/model_forms.py:318
msgid "Bridged interface"
@@ -5395,7 +5395,7 @@ msgstr ""
msgid "Single or three-phase"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1729 netbox/dcim/forms/model_forms.py:1895
#: netbox/dcim/forms/bulk_import.py:1729 netbox/dcim/forms/model_forms.py:1855
#: netbox/dcim/ui/panels.py:109
#: netbox/templates/dcim/virtualdevicecontext.html:30
#: netbox/templates/virtualization/virtualmachine.html:56
@@ -5406,7 +5406,7 @@ msgstr ""
msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1736 netbox/dcim/forms/model_forms.py:1904
#: netbox/dcim/forms/bulk_import.py:1736 netbox/dcim/forms/model_forms.py:1864
#: netbox/dcim/ui/panels.py:114
#: netbox/templates/dcim/virtualdevicecontext.html:41
#: netbox/templates/virtualization/virtualmachine.html:72
@@ -5609,7 +5609,7 @@ msgstr ""
msgid "Mgmt only"
msgstr ""
#: netbox/dcim/forms/filtersets.py:1613 netbox/dcim/forms/model_forms.py:1568
#: netbox/dcim/forms/filtersets.py:1613 netbox/dcim/forms/model_forms.py:1547
#: netbox/dcim/models/device_components.py:791
#: netbox/templates/dcim/interface.html:155
msgid "WWN"
@@ -5710,7 +5710,7 @@ msgstr ""
msgid "Rear ports"
msgstr ""
#: netbox/dcim/forms/mixins.py:158
#: netbox/dcim/forms/mixins.py:155
#, python-brace-format
msgid ""
"The total number of front port positions ({frontport_count}) must match the "
@@ -5782,35 +5782,35 @@ msgid ""
"replaced with the position value when creating a new module."
msgstr ""
#: netbox/dcim/forms/model_forms.py:1252
#: netbox/dcim/forms/model_forms.py:1231
msgid "Console port template"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1260
#: netbox/dcim/forms/model_forms.py:1239
msgid "Console server port template"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1268
#: netbox/dcim/forms/model_forms.py:1247
msgid "Front port template"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1276
#: netbox/dcim/forms/model_forms.py:1255
msgid "Interface template"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1284
#: netbox/dcim/forms/model_forms.py:1263
msgid "Power outlet template"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1292
#: netbox/dcim/forms/model_forms.py:1271
msgid "Power port template"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1300
#: netbox/dcim/forms/model_forms.py:1279
msgid "Rear port template"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1310 netbox/dcim/forms/model_forms.py:1814
#: netbox/dcim/forms/model_forms.py:1289 netbox/dcim/forms/model_forms.py:1774
#: netbox/dcim/tables/connections.py:27
#: netbox/templates/dcim/consoleport.html:17
#: netbox/templates/dcim/consoleserverport.html:73
@@ -5818,14 +5818,14 @@ msgstr ""
msgid "Console Port"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1311 netbox/dcim/forms/model_forms.py:1815
#: netbox/dcim/forms/model_forms.py:1290 netbox/dcim/forms/model_forms.py:1775
#: netbox/templates/dcim/consoleport.html:73
#: netbox/templates/dcim/consoleserverport.html:17
#: netbox/templates/dcim/frontport.html:106
msgid "Console Server Port"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1312 netbox/dcim/forms/model_forms.py:1816
#: netbox/dcim/forms/model_forms.py:1291 netbox/dcim/forms/model_forms.py:1776
#: netbox/templates/circuits/inc/circuit_termination_fields.html:53
#: netbox/templates/dcim/consoleport.html:76
#: netbox/templates/dcim/consoleserverport.html:76
@@ -5837,7 +5837,7 @@ msgstr ""
msgid "Front Port"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1313 netbox/dcim/forms/model_forms.py:1817
#: netbox/dcim/forms/model_forms.py:1292 netbox/dcim/forms/model_forms.py:1777
#: netbox/templates/circuits/inc/circuit_termination_fields.html:54
#: netbox/templates/dcim/consoleport.html:79
#: netbox/templates/dcim/consoleserverport.html:79
@@ -5849,80 +5849,80 @@ msgstr ""
msgid "Rear Port"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1314 netbox/dcim/forms/model_forms.py:1818
#: netbox/dcim/forms/model_forms.py:1293 netbox/dcim/forms/model_forms.py:1778
#: netbox/dcim/tables/connections.py:46 netbox/dcim/tables/devices.py:526
#: netbox/templates/dcim/poweroutlet.html:58
#: netbox/templates/dcim/powerport.html:17
msgid "Power Port"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1315 netbox/dcim/forms/model_forms.py:1819
#: netbox/dcim/forms/model_forms.py:1294 netbox/dcim/forms/model_forms.py:1779
#: netbox/templates/dcim/poweroutlet.html:17
#: netbox/templates/dcim/powerport.html:77
msgid "Power Outlet"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1317 netbox/dcim/forms/model_forms.py:1821
#: netbox/dcim/forms/model_forms.py:1296 netbox/dcim/forms/model_forms.py:1781
msgid "Component Assignment"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1363 netbox/dcim/forms/model_forms.py:1868
#: netbox/dcim/forms/model_forms.py:1342 netbox/dcim/forms/model_forms.py:1828
msgid "An InventoryItem can only be assigned to a single component."
msgstr ""
#: netbox/dcim/forms/model_forms.py:1500
#: netbox/dcim/forms/model_forms.py:1479
msgid "LAG interface"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1523
#: netbox/dcim/forms/model_forms.py:1502
msgid "Filter VLANs available for assignment by group."
msgstr ""
#: netbox/dcim/forms/model_forms.py:1711
#: netbox/dcim/forms/model_forms.py:1671
msgid "Child Device"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1712
#: netbox/dcim/forms/model_forms.py:1672
msgid ""
"Child devices must first be created and assigned to the site and rack of the "
"parent device."
msgstr ""
#: netbox/dcim/forms/model_forms.py:1754
#: netbox/dcim/forms/model_forms.py:1714
msgid "Console port"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1762
#: netbox/dcim/forms/model_forms.py:1722
msgid "Console server port"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1770 netbox/dcim/forms/object_import.py:140
#: netbox/dcim/forms/model_forms.py:1730 netbox/dcim/forms/object_import.py:140
msgid "Front port"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1786
#: netbox/dcim/forms/model_forms.py:1746
msgid "Power outlet"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1802 netbox/dcim/forms/object_import.py:145
#: netbox/dcim/forms/model_forms.py:1762 netbox/dcim/forms/object_import.py:145
msgid "Rear port"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1808
#: netbox/dcim/forms/model_forms.py:1768
#: netbox/templates/dcim/inventoryitem.html:17
msgid "Inventory Item"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1877
#: netbox/dcim/forms/model_forms.py:1837
#: netbox/templates/dcim/inventoryitemrole.html:15
msgid "Inventory Item Role"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1947
#: netbox/dcim/forms/model_forms.py:1907
msgid "VM Interface"
msgstr ""
#: netbox/dcim/forms/model_forms.py:1963 netbox/ipam/forms/filtersets.py:638
#: netbox/dcim/forms/model_forms.py:1923 netbox/ipam/forms/filtersets.py:638
#: netbox/ipam/forms/model_forms.py:323 netbox/ipam/tables/vlans.py:171
#: netbox/templates/virtualization/virtualdisk.html:21
#: netbox/templates/virtualization/virtualmachine.html:12
@@ -5939,7 +5939,7 @@ msgstr ""
msgid "Virtual Machine"
msgstr ""
#: netbox/dcim/forms/model_forms.py:2002
#: netbox/dcim/forms/model_forms.py:1962
msgid "A MAC address can only be assigned to a single object."
msgstr ""
@@ -7679,7 +7679,7 @@ msgstr ""
#: netbox/dcim/tables/devices.py:252 netbox/dcim/tables/devices.py:1125
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1412
#: netbox/dcim/views.py:1749 netbox/dcim/views.py:2577
#: netbox/dcim/views.py:1749 netbox/dcim/views.py:2578
#: netbox/netbox/navigation/menu.py:95 netbox/netbox/navigation/menu.py:259
#: netbox/templates/dcim/buttons/bulk_add_components.html:38
#: netbox/templates/dcim/device/base.html:37
@@ -7726,7 +7726,7 @@ msgstr ""
#: netbox/dcim/tables/devices.py:333 netbox/dcim/tables/devicetypes.py:52
#: netbox/dcim/tables/devicetypes.py:146 netbox/dcim/views.py:1487
#: netbox/dcim/views.py:2663 netbox/netbox/navigation/menu.py:104
#: netbox/dcim/views.py:2664 netbox/netbox/navigation/menu.py:104
#: netbox/templates/dcim/buttons/bulk_add_components.html:66
#: netbox/templates/dcim/device/base.html:52
#: netbox/templates/dcim/devicetype/base.html:49
@@ -7860,7 +7860,7 @@ msgid "Device Count"
msgstr ""
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:1352
#: netbox/dcim/views.py:1689 netbox/dcim/views.py:2512
#: netbox/dcim/views.py:1689 netbox/dcim/views.py:2513
#: netbox/netbox/navigation/menu.py:98
#: netbox/templates/dcim/buttons/bulk_add_components.html:10
#: netbox/templates/dcim/device/base.html:25
@@ -7871,7 +7871,7 @@ msgid "Console Ports"
msgstr ""
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:1367
#: netbox/dcim/views.py:1704 netbox/dcim/views.py:2528
#: netbox/dcim/views.py:1704 netbox/dcim/views.py:2529
#: netbox/netbox/navigation/menu.py:99
#: netbox/templates/dcim/buttons/bulk_add_components.html:17
#: netbox/templates/dcim/device/base.html:28
@@ -7882,7 +7882,7 @@ msgid "Console Server Ports"
msgstr ""
#: netbox/dcim/tables/devicetypes.py:125 netbox/dcim/views.py:1382
#: netbox/dcim/views.py:1719 netbox/dcim/views.py:2544
#: netbox/dcim/views.py:1719 netbox/dcim/views.py:2545
#: netbox/netbox/navigation/menu.py:100
#: netbox/templates/dcim/buttons/bulk_add_components.html:24
#: netbox/templates/dcim/device/base.html:31
@@ -7893,7 +7893,7 @@ msgid "Power Ports"
msgstr ""
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1397
#: netbox/dcim/views.py:1734 netbox/dcim/views.py:2560
#: netbox/dcim/views.py:1734 netbox/dcim/views.py:2561
#: netbox/netbox/navigation/menu.py:101
#: netbox/templates/dcim/buttons/bulk_add_components.html:31
#: netbox/templates/dcim/device/base.html:34
@@ -7904,7 +7904,7 @@ msgid "Power Outlets"
msgstr ""
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1427
#: netbox/dcim/views.py:1764 netbox/dcim/views.py:2599
#: netbox/dcim/views.py:1764 netbox/dcim/views.py:2600
#: netbox/netbox/navigation/menu.py:96
#: netbox/templates/dcim/device/base.html:40
#: netbox/templates/dcim/devicetype/base.html:37
@@ -7914,7 +7914,7 @@ msgid "Front Ports"
msgstr ""
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1442
#: netbox/dcim/views.py:1779 netbox/dcim/views.py:2615
#: netbox/dcim/views.py:1779 netbox/dcim/views.py:2616
#: netbox/netbox/navigation/menu.py:97
#: netbox/templates/dcim/buttons/bulk_add_components.html:45
#: netbox/templates/dcim/device/base.html:43
@@ -7925,7 +7925,7 @@ msgid "Rear Ports"
msgstr ""
#: netbox/dcim/tables/devicetypes.py:140 netbox/dcim/views.py:1472
#: netbox/dcim/views.py:2647 netbox/netbox/navigation/menu.py:103
#: netbox/dcim/views.py:2648 netbox/netbox/navigation/menu.py:103
#: netbox/templates/dcim/buttons/bulk_add_components.html:52
#: netbox/templates/dcim/device/base.html:49
#: netbox/templates/dcim/devicetype/base.html:46
@@ -7933,7 +7933,7 @@ msgid "Device Bays"
msgstr ""
#: netbox/dcim/tables/devicetypes.py:143 netbox/dcim/views.py:1457
#: netbox/dcim/views.py:1794 netbox/dcim/views.py:2631
#: netbox/dcim/views.py:1794 netbox/dcim/views.py:2632
#: netbox/netbox/navigation/menu.py:102
#: netbox/templates/dcim/buttons/bulk_add_components.html:59
#: netbox/templates/dcim/device/base.html:46
@@ -8072,13 +8072,13 @@ msgstr ""
msgid "Reservations"
msgstr ""
#: netbox/dcim/views.py:2458 netbox/netbox/navigation/menu.py:213
#: netbox/dcim/views.py:2459 netbox/netbox/navigation/menu.py:213
#: netbox/templates/ipam/ipaddress.html:118
#: netbox/templates/virtualization/virtualmachine.html:160
msgid "Application Services"
msgstr ""
#: netbox/dcim/views.py:2676 netbox/extras/forms/filtersets.py:427
#: netbox/dcim/views.py:2677 netbox/extras/forms/filtersets.py:427
#: netbox/extras/forms/model_forms.py:691
#: netbox/templates/extras/configcontext.html:10
#: netbox/virtualization/forms/model_forms.py:225
@@ -8086,41 +8086,41 @@ msgstr ""
msgid "Config Context"
msgstr ""
#: netbox/dcim/views.py:2687 netbox/virtualization/views.py:410
#: netbox/dcim/views.py:2688 netbox/virtualization/views.py:410
msgid "Render Config"
msgstr ""
#: netbox/dcim/views.py:2700 netbox/extras/tables/tables.py:713
#: netbox/dcim/views.py:2701 netbox/extras/tables/tables.py:713
#: netbox/netbox/navigation/menu.py:256 netbox/netbox/navigation/menu.py:258
#: netbox/virtualization/views.py:224
msgid "Virtual Machines"
msgstr ""
#: netbox/dcim/views.py:3509
#: netbox/dcim/views.py:3510
#, python-brace-format
msgid "Installed device {device} in bay {device_bay}."
msgstr ""
#: netbox/dcim/views.py:3550
#: netbox/dcim/views.py:3551
#, python-brace-format
msgid "Removed device {device} from bay {device_bay}."
msgstr ""
#: netbox/dcim/views.py:3663 netbox/ipam/tables/ip.py:178
#: netbox/dcim/views.py:3664 netbox/ipam/tables/ip.py:178
msgid "Children"
msgstr ""
#: netbox/dcim/views.py:4136
#: netbox/dcim/views.py:4137
#, python-brace-format
msgid "Added member <a href=\"{url}\">{device}</a>"
msgstr ""
#: netbox/dcim/views.py:4181
#: netbox/dcim/views.py:4182
#, python-brace-format
msgid "Unable to remove master device {device} from the virtual chassis."
msgstr ""
#: netbox/dcim/views.py:4192
#: netbox/dcim/views.py:4193
#, python-brace-format
msgid "Removed {device} from virtual chassis {chassis}"
msgstr ""
@@ -16587,7 +16587,7 @@ msgstr ""
msgid "Missing required value for static query param: '{static_params}'"
msgstr ""
#: netbox/utilities/forms/widgets/modifiers.py:111
#: netbox/utilities/forms/widgets/modifiers.py:141
msgid "(automatically set)"
msgstr ""

View File

@@ -3,9 +3,10 @@ import string
from django.db.models import Q
OBJECTPERMISSION_OBJECT_TYPES = Q(
~Q(app_label__in=['account', 'admin', 'auth', 'contenttypes', 'sessions', 'taggit', 'users']) |
Q(app_label='users', model__in=['objectpermission', 'token', 'group', 'user', 'owner'])
OBJECTPERMISSION_OBJECT_TYPES = (
(Q(public=True) & ~Q(app_label='core', model='objecttype'))
| Q(app_label='core', model__in=['managedfile'])
| Q(app_label='extras', model__in=['scriptmodule', 'taggeditem'])
)
CONSTRAINT_TOKEN_USER = '$user'

View File

@@ -1,6 +1,8 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from utilities.forms.widgets.apiselect import APISelect, APISelectMultiple
__all__ = (
'FilterModifierWidget',
'MODIFIER_EMPTY_FALSE',
@@ -94,9 +96,37 @@ class FilterModifierWidget(forms.Widget):
# to the original widget before rendering
self.original_widget.attrs.update(self.attrs)
# For APISelect/APISelectMultiple widgets, temporarily clear choices to prevent queryset evaluation
original_choices = None
if isinstance(self.original_widget, (APISelect, APISelectMultiple)):
original_choices = self.original_widget.choices
# Only keep selected choices to preserve current selection in HTML
if value:
values = value if isinstance(value, (list, tuple)) else [value]
if hasattr(original_choices, 'queryset'):
queryset = original_choices.queryset
selected_objects = queryset.filter(pk__in=values)
# Build minimal choice list with just the selected values
self.original_widget.choices = [
(obj.pk, str(obj)) for obj in selected_objects
]
else:
self.original_widget.choices = [
choice for choice in original_choices if choice[0] in values
]
else:
# No selection - render empty select element
self.original_widget.choices = []
# Get context from the original widget
original_context = self.original_widget.get_context(name, value, attrs)
# Restore original choices if we modified them
if original_choices is not None:
self.original_widget.choices = original_choices
# Build our wrapper context
context = super().get_context(name, value, attrs)
context['widget']['original_widget'] = original_context['widget']

View File

@@ -5,9 +5,11 @@ from django.conf import settings
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import ImproperlyConfigured
from django.db.models import QuerySet
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import AuthenticationFailed
from netbox.api.authentication import TokenAuthentication
from netbox.plugins import PluginConfig
@@ -50,10 +52,12 @@ class TokenConditionalLoginRequiredMixin(ConditionalLoginRequiredMixin):
# Attempt to authenticate the user using a DRF token, if provided
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
authenticator = TokenAuthentication()
auth_info = authenticator.authenticate(request)
if auth_info is not None:
request.user = auth_info[0] # User object
request.auth = auth_info[1]
try:
if auth_info := authenticator.authenticate(request) is not None:
request.user = auth_info[0] # User object
request.auth = auth_info[1]
except AuthenticationFailed:
return HttpResponseForbidden("Invalid token")
return super().dispatch(request, *args, **kwargs)

View File

@@ -15,7 +15,7 @@ from vpn import models
if TYPE_CHECKING:
from core.graphql.filters import ContentTypeFilter
from ipam.graphql.filters import IPAddressFilter, RouteTargetFilter
from netbox.graphql.filter_lookups import IntegerLookup
from netbox.graphql.filter_lookups import BigIntegerLookup, IntegerLookup
from .enums import *
__all__ = (
@@ -75,7 +75,7 @@ class TunnelFilter(TenancyFilterMixin, PrimaryModelFilter):
ipsec_profile: Annotated['IPSecProfileFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
strawberry_django.filter_field()
)
tunnel_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
tunnel_id: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
terminations: Annotated['TunnelTerminationFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
@@ -187,7 +187,7 @@ class L2VPNFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
type: BaseFilterLookup[Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
identifier: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
import_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = (