Compare commits

...

22 Commits

Author SHA1 Message Date
Martin Hauser
ae1203c07b feat(extras): Add AVIF support for image attachments
Some checks are pending
CI / build (20.x, 3.12) (push) Waiting to run
CI / build (20.x, 3.13) (push) Waiting to run
CI / build (20.x, 3.14) (push) Waiting to run
Extends allowed image file formats to include AVIF for better modern
format support. Introduces a constants mapping for image formats to
centralize file type definitions. Updates form widgets and utilities
to leverage the new constants, enabling more flexible and consistent
image handling.

Fixes #21039
2026-01-17 10:48:43 +01:00
github-actions
586bc132b6 Update source translation strings
Some checks are pending
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
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
CI / build (20.x, 3.12) (push) Waiting to run
CI / build (20.x, 3.13) (push) Waiting to run
CI / build (20.x, 3.14) (push) Waiting to run
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
2026-01-16 16:34:12 -06:00
Micky
3e2a26984f Fixes #21165: Changes filterset to show VLAN group instead of site (#21190) 2026-01-16 09:24:29 -06:00
adionit7
f5f0c19860 Remove obsolete pre-commit hook script
Some checks failed
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
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
The legacy pre-commit hook script was scheduled for removal in NetBox v4.3, as noted in the TODO comment within the file. Users should now use the pre-commit tool instead.
2026-01-16 09:03:08 -05:00
bctiemann
8da9b11ab8 Merge pull request #21154 from netbox-community/21124-moduletype-front-ports
Fixes #21124: Fix rear port selection when creating front ports on a module type
2026-01-16 08:28:39 -05:00
Arthur Hanson
ca67fa9999 Fix #21134: fix bulk rename ModuleType (#21180) 2026-01-16 03:23:28 -06:00
Jeremy Stretch
eff768192e Fixes #21140: Ensure default panel attribute labels are translated (#21153) 2026-01-16 01:35:35 -06:00
github-actions
1e297d55ee Update source translation strings
Some checks failed
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
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
2026-01-16 05:04:49 +00:00
bctiemann
fdb987ef91 Merge pull request #21183 from netbox-community/21178-change-rack-dimensions-display-to-be-more-consistent
Fixes #21178: Add spacing in mounting depth format string
2026-01-15 17:48:39 -05:00
bctiemann
b5a23db43c Merge pull request #21164 from netbox-community/21118-site
fix performance regression for Site save, use bulk_update for cached fields
2026-01-15 17:48:01 -05:00
bctiemann
366b69aff7 Merge pull request #21143 from netbox-community/21050-device-oob-ip-may-become-orphaned
Fixes #21050: Prevent reassignment of OOB IPs
2026-01-15 17:47:00 -05:00
bctiemann
c3e8c5e69c Merge pull request #21100 from netbox-community/21097-graphql-id-lookups
Fixes #21097: Fix comparison lookups for ID filters in GraphQL API
2026-01-15 17:44:22 -05:00
adionit7
b55f36469d Update CodeQL Action from v3 to v4
Some checks failed
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
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
- Update github/codeql-action/init from @v3 to @v4
- Update github/codeql-action/analyze from @v3 to @v4

Fixes #21156
2026-01-15 16:46:25 -05:00
Martin Hauser
1c46215cd5 feat(extras): Allow updates to data_source and data_file via API
Adds support for PATCHing ConfigContext and ConfigContextProfile with
integer IDs for `data_source` and `data_file`.
Adds regression tests to validate assignment and API functionality.

Fixes #20933
2026-01-15 14:37:16 -05:00
Martin Hauser
7fded2fd87 fix(dcim): Add spacing in mounting depth format string
Corrects the format string for mounting depth to include a space
between the value and the unit (`mm`) for consistency with other
measurements.

Fixes #21178
2026-01-15 18:52:25 +01:00
Martin Hauser
0ddc5805c4 fix(core): Use gettext_lazy in data.py
Replace `gettext()` with `gettext_lazy()` to avoid locale-dependent
model serialization (and false-positive pending migration warnings).
Also make a missing `ValidationError` message translatable and
format-safe.

Fixes #21175
2026-01-15 12:47:05 -05:00
github-actions
c1bbc026e2 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
2026-01-15 05:05:36 +00:00
Arthur
8cbfe94fba fix performance regression for Site save, use bulk_update for cached fields 2026-01-14 16:30:40 -08:00
Jeremy Stretch
fff99fd3ff Fixes #21124: Fix rear port selection when creating front ports on a module type 2026-01-14 09:46:04 -05:00
Martin Hauser
f4892caa51 fix(ipam): Prevent reassignment of OOB IPs
Disable reassignment of IP addresses designated as primary or OOB for
parent objects. Adds validation to block changes when an IP is marked as
the OOB IP.

Fixes #21050
2026-01-13 18:13:31 +01:00
Jeremy Stretch
a54ad24b47 Fixes #21097: Fix comparison lookups for ID filters in GraphQL API 2026-01-08 16:34:13 -05:00
21 changed files with 328 additions and 205 deletions

View File

@@ -30,13 +30,13 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v4
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
config-file: .github/codeql/codeql-config.yml config-file: .github/codeql/codeql-config.yml
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v4
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -44,3 +44,4 @@ class DataFileSerializer(NetBoxModelSerializer):
'id', 'url', 'display_url', 'display', 'source', 'path', 'last_updated', 'size', 'hash', 'id', 'url', 'display_url', 'display', 'source', 'path', 'last_updated', 'size', 'hash',
] ]
brief_fields = ('id', 'url', 'display', 'path') brief_fields = ('id', 'url', 'display', 'path')
read_only_fields = ['path', 'last_updated', 'size', 'hash']

View File

@@ -12,7 +12,7 @@ from django.core.validators import RegexValidator
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy as _
from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED
from netbox.models import PrimaryModel from netbox.models import PrimaryModel
@@ -128,7 +128,9 @@ class DataSource(JobsMixin, PrimaryModel):
# Ensure URL scheme matches selected type # Ensure URL scheme matches selected type
if self.backend_class.is_local and self.url_scheme not in ('file', ''): if self.backend_class.is_local and self.url_scheme not in ('file', ''):
raise ValidationError({ raise ValidationError({
'source_url': "URLs for local sources must start with file:// (or specify no scheme)" 'source_url': _("URLs for local sources must start with {scheme} (or specify no scheme)").format(
scheme='file://'
)
}) })
def save(self, *args, **kwargs): def save(self, *args, **kwargs):

View File

@@ -140,9 +140,6 @@ class FrontPortFormMixin(forms.Form):
widget=forms.SelectMultiple(attrs={'size': 8}) widget=forms.SelectMultiple(attrs={'size': 8})
) )
port_mapping_model = PortMapping
parent_field = 'device'
def clean(self): def clean(self):
super().clean() super().clean()
@@ -203,3 +200,22 @@ class FrontPortFormMixin(forms.Form):
using=connection, using=connection,
update_fields=None update_fields=None
) )
def _get_rear_port_choices(self, parent_filter, front_port):
"""
Return a list of choices representing each available rear port & position pair on the parent object (identified
by a Q filter), excluding those assigned to the specified instance.
"""
occupied_rear_port_positions = [
f'{mapping.rear_port_id}:{mapping.rear_port_position}'
for mapping in self.port_mapping_model.objects.filter(parent_filter).exclude(front_port=front_port.pk)
]
choices = []
for rear_port in self.rear_port_model.objects.filter(parent_filter):
for i in range(1, rear_port.positions + 1):
pair_id = f'{rear_port.pk}:{i}'
if pair_id not in occupied_rear_port_positions:
pair_label = f'{rear_port.name}:{i}'
choices.append((pair_id, pair_label))
return choices

View File

@@ -1124,9 +1124,8 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
), ),
) )
# Override FrontPortFormMixin attrs
port_mapping_model = PortTemplateMapping port_mapping_model = PortTemplateMapping
parent_field = 'device_type' rear_port_model = RearPortTemplate
class Meta: class Meta:
model = FrontPortTemplate model = FrontPortTemplate
@@ -1137,13 +1136,14 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Populate rear port choices based on parent DeviceType or ModuleType
if device_type_id := self.data.get('device_type') or self.initial.get('device_type'): if device_type_id := self.data.get('device_type') or self.initial.get('device_type'):
device_type = DeviceType.objects.get(pk=device_type_id) parent_filter = Q(device_type=device_type_id)
elif module_type_id := self.data.get('module_type') or self.initial.get('module_type'):
parent_filter = Q(module_type=module_type_id)
else: else:
return return
self.fields['rear_ports'].choices = self._get_rear_port_choices(parent_filter, self.instance)
# Populate rear port choices
self.fields['rear_ports'].choices = self._get_rear_port_choices(device_type, self.instance)
# Set initial rear port mappings # Set initial rear port mappings
if self.instance.pk: if self.instance.pk:
@@ -1152,27 +1152,6 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
for mapping in PortTemplateMapping.objects.filter(front_port_id=self.instance.pk) for mapping in PortTemplateMapping.objects.filter(front_port_id=self.instance.pk)
] ]
def _get_rear_port_choices(self, device_type, front_port):
"""
Return a list of choices representing each available rear port & position pair on the device type, excluding
those assigned to the specified instance.
"""
occupied_rear_port_positions = [
f'{mapping.rear_port_id}:{mapping.rear_port_position}'
for mapping in device_type.port_mappings.exclude(front_port=front_port.pk)
]
choices = []
for rear_port in RearPortTemplate.objects.filter(device_type=device_type):
for i in range(1, rear_port.positions + 1):
pair_id = f'{rear_port.pk}:{i}'
if pair_id not in occupied_rear_port_positions:
pair_label = f'{rear_port.name}:{i}'
choices.append(
(pair_id, pair_label)
)
return choices
class RearPortTemplateForm(ModularComponentTemplateForm): class RearPortTemplateForm(ModularComponentTemplateForm):
fieldsets = ( fieldsets = (
@@ -1619,6 +1598,9 @@ class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
), ),
) )
port_mapping_model = PortMapping
rear_port_model = RearPort
class Meta: class Meta:
model = FrontPort model = FrontPort
fields = [ fields = [
@@ -1629,13 +1611,12 @@ class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Populate rear port choices
if device_id := self.data.get('device') or self.initial.get('device'): if device_id := self.data.get('device') or self.initial.get('device'):
device = Device.objects.get(pk=device_id) parent_filter = Q(device=device_id)
else: else:
return return
self.fields['rear_ports'].choices = self._get_rear_port_choices(parent_filter, self.instance)
# Populate rear port choices
self.fields['rear_ports'].choices = self._get_rear_port_choices(device, self.instance)
# Set initial rear port mappings # Set initial rear port mappings
if self.instance.pk: if self.instance.pk:
@@ -1644,27 +1625,6 @@ class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
for mapping in PortMapping.objects.filter(front_port_id=self.instance.pk) for mapping in PortMapping.objects.filter(front_port_id=self.instance.pk)
] ]
def _get_rear_port_choices(self, device, front_port):
"""
Return a list of choices representing each available rear port & position pair on the device, excluding those
assigned to the specified instance.
"""
occupied_rear_port_positions = [
f'{mapping.rear_port_id}:{mapping.rear_port_position}'
for mapping in device.port_mappings.exclude(front_port=front_port.pk)
]
choices = []
for rear_port in RearPort.objects.filter(device=device):
for i in range(1, rear_port.positions + 1):
pair_id = f'{rear_port.pk}:{i}'
if pair_id not in occupied_rear_port_positions:
pair_label = f'{rear_port.name}:{i}'
choices.append(
(pair_id, pair_label)
)
return choices
class RearPortForm(ModularDeviceComponentForm): class RearPortForm(ModularDeviceComponentForm):
fieldsets = ( fieldsets = (

View File

@@ -211,12 +211,16 @@ def sync_cached_scope_fields(instance, created, **kwargs):
for model in (Prefix, Cluster, WirelessLAN): for model in (Prefix, Cluster, WirelessLAN):
qs = model.objects.filter(**filters) qs = model.objects.filter(**filters)
# Bulk update cached fields to avoid O(N) performance issues with large datasets.
# This does not trigger post_save signals, avoiding spurious change log entries.
objects_to_update = []
for obj in qs: for obj in qs:
# Recompute cache using the same logic as save() # Recompute cache using the same logic as save()
obj.cache_related_objects() obj.cache_related_objects()
obj.save(update_fields=[ objects_to_update.append(obj)
'_location',
'_site', if objects_to_update:
'_site_group', model.objects.bulk_update(
'_region', objects_to_update,
]) ['_location', '_site', '_site_group', '_region']
)

View File

@@ -31,7 +31,7 @@ class RackDimensionsPanel(panels.ObjectAttributesPanel):
outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display') 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_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display')
outer_depth = attrs.NumericAttr('outer_depth', 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='{}mm') mounting_depth = attrs.TextAttr('mounting_depth', format_string='{} mm')
class RackNumberingPanel(panels.ObjectAttributesPanel): class RackNumberingPanel(panels.ObjectAttributesPanel):

View File

@@ -1845,6 +1845,7 @@ class ModuleTypeBulkEditView(generic.BulkEditView):
class ModuleTypeBulkRenameView(generic.BulkRenameView): class ModuleTypeBulkRenameView(generic.BulkRenameView):
queryset = ModuleType.objects.all() queryset = ModuleType.objects.all()
filterset = filtersets.ModuleTypeFilterSet filterset = filtersets.ModuleTypeFilterSet
field_name = 'model'
@register_model_view(ModuleType, 'bulk_delete', path='delete', detail=False) @register_model_view(ModuleType, 'bulk_delete', path='delete', detail=False)

View File

@@ -28,7 +28,7 @@ class ConfigContextProfileSerializer(PrimaryModelSerializer):
) )
data_file = DataFileSerializer( data_file = DataFileSerializer(
nested=True, nested=True,
read_only=True required=False
) )
class Meta: class Meta:
@@ -143,7 +143,7 @@ class ConfigContextSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedM
) )
data_file = DataFileSerializer( data_file = DataFileSerializer(
nested=True, nested=True,
read_only=True required=False
) )
class Meta: class Meta:

View File

@@ -4,6 +4,17 @@ from extras.choices import LogLevelChoices
# Custom fields # Custom fields
CUSTOMFIELD_EMPTY_VALUES = (None, '', []) CUSTOMFIELD_EMPTY_VALUES = (None, '', [])
# ImageAttachment
IMAGE_ATTACHMENT_IMAGE_FORMATS = {
'avif': 'image/avif',
'bmp': 'image/bmp',
'gif': 'image/gif',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'png': 'image/png',
'webp': 'image/webp',
}
# Template Export # Template Export
DEFAULT_MIME_TYPE = 'text/plain; charset=utf-8' DEFAULT_MIME_TYPE = 'text/plain; charset=utf-8'

View File

@@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
from core.forms.mixins import SyncedDataMixin from core.forms.mixins import SyncedDataMixin
from core.models import ObjectType from core.models import ObjectType
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
from extras.constants import IMAGE_ATTACHMENT_IMAGE_FORMATS
from extras.choices import * from extras.choices import *
from extras.models import * from extras.models import *
from netbox.events import get_event_type_choices from netbox.events import get_event_type_choices
@@ -784,8 +785,11 @@ class ImageAttachmentForm(forms.ModelForm):
fields = [ fields = [
'image', 'name', 'description', 'image', 'name', 'description',
] ]
help_texts = { # Explicitly set 'image/avif' to support AVIF selection in Firefox
'name': _("If no name is specified, the file name will be used.") widgets = {
'image': forms.ClearableFileInput(
attrs={'accept': ','.join(sorted(set(IMAGE_ATTACHMENT_IMAGE_FORMATS.values())))}
),
} }

View File

@@ -1,4 +1,5 @@
import datetime import datetime
import hashlib
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.urls import reverse from django.urls import reverse
@@ -7,7 +8,7 @@ from rest_framework import status
from core.choices import ManagedFileRootPathChoices from core.choices import ManagedFileRootPathChoices
from core.events import * from core.events import *
from core.models import ObjectType from core.models import DataFile, DataSource, ObjectType
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site
from extras.choices import * from extras.choices import *
from extras.models import * from extras.models import *
@@ -731,6 +732,51 @@ class ConfigContextProfileTest(APIViewTestCases.APIViewTestCase):
) )
ConfigContextProfile.objects.bulk_create(profiles) ConfigContextProfile.objects.bulk_create(profiles)
def test_update_data_source_and_data_file(self):
"""
Regression test: Ensure data_source and data_file can be assigned via the API.
This specifically covers PATCHing a ConfigContext with integer IDs for both fields.
"""
self.add_permissions(
'core.view_datafile',
'core.view_datasource',
'extras.view_configcontextprofile',
'extras.change_configcontextprofile',
)
config_context_profile = ConfigContextProfile.objects.first()
# Create a data source and file
datasource = DataSource.objects.create(
name='Data Source 1',
type='local',
source_url='file:///tmp/netbox-datasource/',
)
# Generate a valid dummy YAML file
file_data = b'profile: configcontext\n'
datafile = DataFile.objects.create(
source=datasource,
path='dir1/file1.yml',
last_updated=now(),
size=len(file_data),
hash=hashlib.sha256(file_data).hexdigest(),
data=file_data,
)
url = self._get_detail_url(config_context_profile)
payload = {
'data_source': datasource.pk,
'data_file': datafile.pk,
}
response = self.client.patch(url, payload, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
config_context_profile.refresh_from_db()
self.assertEqual(config_context_profile.data_source_id, datasource.pk)
self.assertEqual(config_context_profile.data_file_id, datafile.pk)
self.assertEqual(response.data['data_source']['id'], datasource.pk)
self.assertEqual(response.data['data_file']['id'], datafile.pk)
class ConfigContextTest(APIViewTestCases.APIViewTestCase): class ConfigContextTest(APIViewTestCases.APIViewTestCase):
model = ConfigContext model = ConfigContext
@@ -812,6 +858,51 @@ class ConfigContextTest(APIViewTestCases.APIViewTestCase):
rendered_context = device.get_config_context() rendered_context = device.get_config_context()
self.assertEqual(rendered_context['bar'], 456) self.assertEqual(rendered_context['bar'], 456)
def test_update_data_source_and_data_file(self):
"""
Regression test: Ensure data_source and data_file can be assigned via the API.
This specifically covers PATCHing a ConfigContext with integer IDs for both fields.
"""
self.add_permissions(
'core.view_datafile',
'core.view_datasource',
'extras.view_configcontext',
'extras.change_configcontext',
)
config_context = ConfigContext.objects.first()
# Create a data source and file
datasource = DataSource.objects.create(
name='Data Source 1',
type='local',
source_url='file:///tmp/netbox-datasource/',
)
# Generate a valid dummy YAML file
file_data = b'context: config\n'
datafile = DataFile.objects.create(
source=datasource,
path='dir1/file1.yml',
last_updated=now(),
size=len(file_data),
hash=hashlib.sha256(file_data).hexdigest(),
data=file_data,
)
url = self._get_detail_url(config_context)
payload = {
'data_source': datasource.pk,
'data_file': datafile.pk,
}
response = self.client.patch(url, payload, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
config_context.refresh_from_db()
self.assertEqual(config_context.data_source_id, datasource.pk)
self.assertEqual(config_context.data_file_id, datafile.pk)
self.assertEqual(response.data['data_source']['id'], datasource.pk)
self.assertEqual(response.data['data_file']['id'], datafile.pk)
class ConfigTemplateTest(APIViewTestCases.APIViewTestCase): class ConfigTemplateTest(APIViewTestCases.APIViewTestCase):
model = ConfigTemplate model = ConfigTemplate

View File

@@ -10,6 +10,7 @@ from taggit.managers import _TaggableManager
from netbox.context import current_request from netbox.context import current_request
from .constants import IMAGE_ATTACHMENT_IMAGE_FORMATS
from .validators import CustomValidator from .validators import CustomValidator
__all__ = ( __all__ = (
@@ -78,7 +79,7 @@ def image_upload(instance, filename):
""" """
upload_dir = 'image-attachments' upload_dir = 'image-attachments'
default_filename = 'unnamed' default_filename = 'unnamed'
allowed_img_extensions = ('bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp') allowed_img_extensions = IMAGE_ATTACHMENT_IMAGE_FORMATS.keys()
# Normalize Windows paths and create a Path object. # Normalize Windows paths and create a Path object.
normalized_filename = str(filename).replace('\\', '/') normalized_filename = str(filename).replace('\\', '/')

View File

@@ -538,7 +538,7 @@ class VLANFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
FieldSet('qinq_role', 'qinq_svlan_id', name=_('Q-in-Q/802.1ad')), FieldSet('qinq_role', 'qinq_svlan_id', name=_('Q-in-Q/802.1ad')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
) )
selector_fields = ('filter_id', 'q', 'site_id') selector_fields = ('filter_id', 'q', 'group_id')
region_id = DynamicModelMultipleChoiceField( region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False, required=False,

View File

@@ -372,8 +372,8 @@ class IPAddressForm(TenancyForm, PrimaryModelForm):
'virtual_machine_id': instance.assigned_object.virtual_machine.pk, 'virtual_machine_id': instance.assigned_object.virtual_machine.pk,
}) })
# Disable object assignment fields if the IP address is designated as primary # Disable object assignment fields if the IP address is designated as primary or OOB
if self.initial.get('primary_for_parent'): if self.initial.get('primary_for_parent') or self.initial.get('oob_for_parent'):
self.fields['interface'].disabled = True self.fields['interface'].disabled = True
self.fields['vminterface'].disabled = True self.fields['vminterface'].disabled = True
self.fields['fhrpgroup'].disabled = True self.fields['fhrpgroup'].disabled = True

View File

@@ -940,6 +940,13 @@ class IPAddress(ContactsMixin, PrimaryModel):
_("Cannot reassign IP address while it is designated as the primary IP for the parent object") _("Cannot reassign IP address while it is designated as the primary IP for the parent object")
) )
# can't use is_oob_ip as self.assigned_object might be changed
if hasattr(original_parent, 'oob_ip') and original_parent.oob_ip_id == self.pk:
if parent != original_parent:
raise ValidationError(
_("Cannot reassign IP address while it is designated as the OOB IP for the parent object")
)
# Validate IP status selection # Validate IP status selection
if self.status == IPAddressStatusChoices.STATUS_SLAAC and self.family != 6: if self.status == IPAddressStatusChoices.STATUS_SLAAC and self.family != 6:
raise ValidationError({ raise ValidationError({

View File

@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
import strawberry_django import strawberry_django
from strawberry import ID from strawberry import ID
from strawberry_django import FilterLookup from strawberry_django import ComparisonFilterLookup, FilterLookup
from core.graphql.filter_mixins import ChangeLoggingMixin from core.graphql.filter_mixins import ChangeLoggingMixin
from extras.graphql.filter_mixins import CustomFieldsFilterMixin, JournalEntriesFilterMixin, TagsFilterMixin from extras.graphql.filter_mixins import CustomFieldsFilterMixin, JournalEntriesFilterMixin, TagsFilterMixin
@@ -23,7 +23,7 @@ __all__ = (
@dataclass @dataclass
class BaseModelFilter: class BaseModelFilter:
id: FilterLookup[ID] | None = strawberry_django.filter_field() id: ComparisonFilterLookup[ID] | None = strawberry_django.filter_field()
class ChangeLoggedModelFilter(ChangeLoggingMixin, BaseModelFilter): class ChangeLoggedModelFilter(ChangeLoggingMixin, BaseModelFilter):

View File

@@ -164,7 +164,7 @@ class ObjectAttributesPanel(ObjectPanel, metaclass=ObjectAttributesPanelMeta):
""" """
label = name[:1].upper() + name[1:] label = name[:1].upper() + name[1:]
label = label.replace('_', ' ') label = label.replace('_', ' ')
return label return _(label)
def get_context(self, context): def get_context(self, context):
# Determine which attributes to display in the panel based on only/exclude args # Determine which attributes to display in the panel based on only/exclude args

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-13 05:05+0000\n" "POT-Creation-Date: 2026-01-17 05:02+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -806,7 +806,7 @@ msgstr ""
#: netbox/circuits/forms/model_forms.py:335 #: 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: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: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/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:84 netbox/ipam/forms/model_forms.py:115
#: netbox/ipam/forms/model_forms.py:136 netbox/ipam/forms/model_forms.py:160 #: 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/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:399 netbox/dcim/forms/filtersets.py:865
#: netbox/dcim/forms/filtersets.py:1872 netbox/dcim/forms/filtersets.py:1912 #: 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:255 netbox/dcim/forms/model_forms.py:1214
#: netbox/dcim/forms/model_forms.py:1737 netbox/dcim/forms/object_import.py:182 #: 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:171 netbox/dcim/tables/devices.py:857
#: netbox/dcim/tables/devices.py:983 netbox/dcim/tables/devicetypes.py:317 #: netbox/dcim/tables/devices.py:983 netbox/dcim/tables/devicetypes.py:317
#: netbox/dcim/tables/racks.py:117 netbox/extras/filtersets.py:708 #: 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/bulk_import.py:258
#: netbox/circuits/forms/model_forms.py:358 #: netbox/circuits/forms/model_forms.py:358
#: netbox/circuits/tables/virtual_circuits.py:108 #: netbox/circuits/tables/virtual_circuits.py:108
#: netbox/dcim/forms/bulk_import.py:1324 netbox/dcim/forms/model_forms.py:1309 #: netbox/dcim/forms/bulk_import.py:1324 netbox/dcim/forms/model_forms.py:1288
#: netbox/dcim/forms/model_forms.py:1578 netbox/dcim/forms/model_forms.py:1778 #: netbox/dcim/forms/model_forms.py:1557 netbox/dcim/forms/model_forms.py:1738
#: netbox/dcim/forms/model_forms.py:1813 netbox/dcim/forms/model_forms.py:1938 #: 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/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/bulk_import.py:319 netbox/ipam/forms/model_forms.py:280
#: netbox/ipam/forms/model_forms.py:289 netbox/ipam/tables/fhrp.py:61 #: 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:1866 netbox/dcim/forms/filtersets.py:1907
#: netbox/dcim/forms/filtersets.py:2000 netbox/dcim/forms/filtersets.py:2024 #: 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/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:943 netbox/dcim/forms/model_forms.py:1355
#: netbox/dcim/forms/model_forms.py:1889 netbox/dcim/forms/model_forms.py:1962 #: 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/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/connections.py:41 netbox/dcim/tables/connections.py:60
#: netbox/dcim/tables/devices.py:291 netbox/dcim/tables/devices.py:386 #: netbox/dcim/tables/devices.py:291 netbox/dcim/tables/devices.py:386
@@ -2435,7 +2435,7 @@ msgstr ""
msgid "Change logging is not supported for this object type ({type})." msgid "Change logging is not supported for this object type ({type})."
msgstr "" msgstr ""
#: netbox/core/models/config.py:21 netbox/core/models/data.py:282 #: netbox/core/models/config.py:21 netbox/core/models/data.py:284
#: netbox/core/models/files.py:29 netbox/core/models/jobs.py:60 #: netbox/core/models/files.py:29 netbox/core/models/jobs.py:60
#: netbox/extras/models/models.py:847 netbox/extras/models/notifications.py:39 #: netbox/extras/models/models.py:847 netbox/extras/models/notifications.py:39
#: netbox/extras/models/notifications.py:195 #: netbox/extras/models/notifications.py:195
@@ -2541,58 +2541,63 @@ msgstr ""
msgid "Unknown backend type: {type}" msgid "Unknown backend type: {type}"
msgstr "" msgstr ""
#: netbox/core/models/data.py:180 #: netbox/core/models/data.py:131
#, python-brace-format
msgid "URLs for local sources must start with {scheme} (or specify no scheme)"
msgstr ""
#: netbox/core/models/data.py:182
msgid "Cannot initiate sync; syncing already in progress." msgid "Cannot initiate sync; syncing already in progress."
msgstr "" msgstr ""
#: netbox/core/models/data.py:193 #: netbox/core/models/data.py:195
msgid "" msgid ""
"There was an error initializing the backend. A dependency needs to be " "There was an error initializing the backend. A dependency needs to be "
"installed: " "installed: "
msgstr "" msgstr ""
#: netbox/core/models/data.py:286 netbox/core/models/files.py:33 #: netbox/core/models/data.py:288 netbox/core/models/files.py:33
#: netbox/netbox/models/features.py:67 #: netbox/netbox/models/features.py:67
msgid "last updated" msgid "last updated"
msgstr "" msgstr ""
#: netbox/core/models/data.py:296 netbox/dcim/models/cables.py:622 #: netbox/core/models/data.py:298 netbox/dcim/models/cables.py:622
msgid "path" msgid "path"
msgstr "" msgstr ""
#: netbox/core/models/data.py:299 #: netbox/core/models/data.py:301
msgid "File path relative to the data source's root" msgid "File path relative to the data source's root"
msgstr "" msgstr ""
#: netbox/core/models/data.py:303 netbox/ipam/models/ip.py:507 #: netbox/core/models/data.py:305 netbox/ipam/models/ip.py:507
msgid "size" msgid "size"
msgstr "" msgstr ""
#: netbox/core/models/data.py:306 #: netbox/core/models/data.py:308
msgid "hash" msgid "hash"
msgstr "" msgstr ""
#: netbox/core/models/data.py:310 #: netbox/core/models/data.py:312
msgid "Length must be 64 hexadecimal characters." msgid "Length must be 64 hexadecimal characters."
msgstr "" msgstr ""
#: netbox/core/models/data.py:312 #: netbox/core/models/data.py:314
msgid "SHA256 hash of the file data" msgid "SHA256 hash of the file data"
msgstr "" msgstr ""
#: netbox/core/models/data.py:326 #: netbox/core/models/data.py:328
msgid "data file" msgid "data file"
msgstr "" msgstr ""
#: netbox/core/models/data.py:327 #: netbox/core/models/data.py:329
msgid "data files" msgid "data files"
msgstr "" msgstr ""
#: netbox/core/models/data.py:400 #: netbox/core/models/data.py:402
msgid "auto sync record" msgid "auto sync record"
msgstr "" msgstr ""
#: netbox/core/models/data.py:401 #: netbox/core/models/data.py:403
msgid "auto sync records" msgid "auto sync records"
msgstr "" msgstr ""
@@ -3079,8 +3084,8 @@ msgstr ""
#: netbox/dcim/forms/filtersets.py:768 netbox/dcim/forms/filtersets.py:783 #: 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: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: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:523 netbox/dcim/forms/model_forms.py:1206
#: netbox/dcim/forms/model_forms.py:1729 netbox/dcim/forms/object_import.py:177 #: 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:702 netbox/dcim/tables/devices.py:916
#: netbox/dcim/tables/devices.py:1003 netbox/dcim/tables/devices.py:1156 #: 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 #: netbox/ipam/forms/bulk_import.py:578 netbox/ipam/forms/model_forms.py:755
@@ -3212,7 +3217,7 @@ msgstr ""
#: netbox/dcim/choices.py:885 netbox/dcim/choices.py:1351 #: 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/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/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/netbox/navigation/menu.py:151
#: netbox/templates/dcim/interface.html:280 #: netbox/templates/dcim/interface.html:280
msgid "Wireless" msgid "Wireless"
@@ -3830,7 +3835,7 @@ msgstr ""
#: netbox/dcim/filtersets.py:1242 netbox/dcim/forms/filtersets.py:906 #: 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/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/dcim/models/devices.py:1330 netbox/virtualization/filtersets.py:211
#: netbox/virtualization/filtersets.py:284 #: netbox/virtualization/filtersets.py:284
#: netbox/virtualization/forms/filtersets.py:187 #: netbox/virtualization/forms/filtersets.py:187
@@ -3996,7 +4001,7 @@ msgstr ""
#: netbox/dcim/filtersets.py:1942 netbox/dcim/forms/bulk_edit.py:1509 #: 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/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/models/device_components.py:866
#: netbox/dcim/tables/devices.py:660 netbox/ipam/filtersets.py:345 #: netbox/dcim/tables/devices.py:660 netbox/ipam/filtersets.py:345
#: netbox/ipam/filtersets.py:356 netbox/ipam/filtersets.py:489 #: netbox/ipam/filtersets.py:356 netbox/ipam/filtersets.py:489
@@ -4055,7 +4060,7 @@ msgid "VLAN Translation Policy (ID)"
msgstr "" msgstr ""
#: netbox/dcim/filtersets.py:1970 netbox/dcim/forms/filtersets.py:1633 #: 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/dcim/models/device_components.py:668
#: netbox/ipam/forms/filtersets.py:518 netbox/ipam/forms/model_forms.py:700 #: netbox/ipam/forms/filtersets.py:518 netbox/ipam/forms/model_forms.py:700
#: netbox/templates/ipam/vlantranslationpolicy.html:11 #: netbox/templates/ipam/vlantranslationpolicy.html:11
@@ -4109,14 +4114,14 @@ msgstr ""
msgid "Primary MAC address (ID)" msgid "Primary MAC address (ID)"
msgstr "" 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/filtersets.py:295
#: netbox/virtualization/forms/model_forms.py:302 #: netbox/virtualization/forms/model_forms.py:302
msgid "Primary MAC address" msgid "Primary MAC address"
msgstr "" msgstr ""
#: netbox/dcim/filtersets.py:2079 netbox/dcim/filtersets.py:2091 #: 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 #: netbox/templates/dcim/virtualdevicecontext.html:15
msgid "Virtual Device Context" msgid "Virtual Device Context"
msgstr "" msgstr ""
@@ -4238,7 +4243,7 @@ msgstr ""
#: netbox/dcim/forms/filtersets.py:1917 netbox/dcim/forms/model_forms.py:211 #: 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: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: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/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/devices.py:174 netbox/dcim/tables/devices.py:986
#: netbox/dcim/tables/devicetypes.py:86 netbox/dcim/tables/devicetypes.py:321 #: netbox/dcim/tables/devicetypes.py:86 netbox/dcim/tables/devicetypes.py:321
@@ -4416,7 +4421,7 @@ msgstr ""
#: netbox/dcim/forms/filtersets.py:524 netbox/dcim/forms/filtersets.py:667 #: 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/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: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 #: netbox/templates/dcim/device_edit.html:22
msgid "Hardware" msgid "Hardware"
msgstr "" msgstr ""
@@ -4442,8 +4447,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:531 netbox/dcim/forms/model_forms.py:373 #: 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: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: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:1120 netbox/dcim/forms/model_forms.py:1160
#: netbox/dcim/forms/model_forms.py:1199 netbox/dcim/forms/object_create.py:117 #: 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/dcim/tables/devicetypes.py:83 netbox/templates/dcim/devicebay.html:52
#: netbox/templates/dcim/module.html:61 #: netbox/templates/dcim/module.html:61
msgid "Device Type" msgid "Device Type"
@@ -4472,7 +4477,7 @@ msgstr ""
#: netbox/dcim/forms/model_forms.py:431 netbox/dcim/forms/model_forms.py:1003 #: 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: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: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/forms/object_create.py:118 netbox/dcim/tables/modules.py:51
#: netbox/dcim/tables/modules.py:94 netbox/templates/dcim/module.html:92 #: netbox/dcim/tables/modules.py:94 netbox/templates/dcim/module.html:92
#: netbox/templates/dcim/modulebay.html:66 #: netbox/templates/dcim/modulebay.html:66
@@ -4656,8 +4661,8 @@ msgid "Allocated power draw (watts)"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1058 netbox/dcim/forms/bulk_import.py:885 #: 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:1060 netbox/dcim/forms/model_forms.py:1425
#: netbox/dcim/forms/model_forms.py:1794 netbox/dcim/forms/object_import.py:56 #: netbox/dcim/forms/model_forms.py:1754 netbox/dcim/forms/object_import.py:56
msgid "Power port" msgid "Power port"
msgstr "" msgstr ""
@@ -4692,7 +4697,7 @@ msgid "Wireless role"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1268 netbox/dcim/forms/model_forms.py:766 #: 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/consoleport.html:24
#: netbox/templates/dcim/consoleserverport.html:24 #: netbox/templates/dcim/consoleserverport.html:24
#: netbox/templates/dcim/frontport.html:24 #: netbox/templates/dcim/frontport.html:24
@@ -4710,7 +4715,7 @@ msgstr ""
msgid "LAG" msgid "LAG"
msgstr "" 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" msgid "Virtual device contexts"
msgstr "" msgstr ""
@@ -4739,7 +4744,7 @@ msgid "Mode"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1458 netbox/dcim/forms/bulk_import.py:993 #: 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/ipam/forms/filtersets.py:568 netbox/ipam/models/vlans.py:93
#: netbox/virtualization/forms/bulk_edit.py:205 #: netbox/virtualization/forms/bulk_edit.py:205
#: netbox/virtualization/forms/bulk_import.py:185 #: netbox/virtualization/forms/bulk_import.py:185
@@ -4748,7 +4753,7 @@ msgid "VLAN group"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1467 netbox/dcim/forms/bulk_import.py:1000 #: 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_edit.py:213
#: netbox/virtualization/forms/bulk_import.py:192 #: netbox/virtualization/forms/bulk_import.py:192
#: netbox/virtualization/forms/model_forms.py:331 #: netbox/virtualization/forms/model_forms.py:331
@@ -4756,7 +4761,7 @@ msgid "Untagged VLAN"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1476 netbox/dcim/forms/bulk_import.py:1007 #: 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_edit.py:221
#: netbox/virtualization/forms/bulk_import.py:199 #: netbox/virtualization/forms/bulk_import.py:199
#: netbox/virtualization/forms/model_forms.py:340 #: netbox/virtualization/forms/model_forms.py:340
@@ -4772,18 +4777,18 @@ msgid "Remove tagged VLANs"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1499 netbox/dcim/forms/bulk_import.py:1020 #: 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/bulk_import.py:212
#: netbox/virtualization/forms/model_forms.py:349 #: netbox/virtualization/forms/model_forms.py:349
msgid "Q-in-Q Service VLAN" msgid "Q-in-Q Service VLAN"
msgstr "" 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 #: netbox/wireless/forms/filtersets.py:26
msgid "Wireless LAN group" msgid "Wireless LAN group"
msgstr "" 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/dcim/tables/devices.py:653 netbox/netbox/navigation/menu.py:153
#: netbox/templates/dcim/interface.html:350 #: netbox/templates/dcim/interface.html:350
#: netbox/wireless/tables/wirelesslan.py:20 #: netbox/wireless/tables/wirelesslan.py:20
@@ -4791,7 +4796,7 @@ msgid "Wireless LANs"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1528 netbox/dcim/forms/filtersets.py:1550 #: 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/ipam/forms/bulk_edit.py:310 netbox/ipam/forms/filtersets.py:184
#: netbox/netbox/navigation/menu.py:109 #: netbox/netbox/navigation/menu.py:109
#: netbox/templates/dcim/interface.html:141 #: netbox/templates/dcim/interface.html:141
@@ -4803,18 +4808,18 @@ msgid "Addressing"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1529 netbox/dcim/forms/filtersets.py:808 #: 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 #: netbox/virtualization/forms/model_forms.py:370
msgid "Operation" msgid "Operation"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1530 netbox/dcim/forms/filtersets.py:1551 #: 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/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" msgid "PoE"
msgstr "" 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/templates/dcim/interface.html:105
#: netbox/virtualization/forms/bulk_edit.py:237 #: netbox/virtualization/forms/bulk_edit.py:237
#: netbox/virtualization/forms/model_forms.py:371 #: netbox/virtualization/forms/model_forms.py:371
@@ -4822,7 +4827,7 @@ msgid "Related Interfaces"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1533 netbox/dcim/forms/filtersets.py:1552 #: 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/bulk_edit.py:240
#: netbox/virtualization/forms/filtersets.py:215 #: netbox/virtualization/forms/filtersets.py:215
#: netbox/virtualization/forms/model_forms.py:374 #: netbox/virtualization/forms/model_forms.py:374
@@ -5101,13 +5106,13 @@ msgstr ""
msgid "Electrical phase (for three-phase circuits)" msgid "Electrical phase (for three-phase circuits)"
msgstr "" 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/bulk_import.py:169
#: netbox/virtualization/forms/model_forms.py:310 #: netbox/virtualization/forms/model_forms.py:310
msgid "Parent interface" msgid "Parent interface"
msgstr "" 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/bulk_import.py:176
#: netbox/virtualization/forms/model_forms.py:318 #: netbox/virtualization/forms/model_forms.py:318
msgid "Bridged interface" msgid "Bridged interface"
@@ -5390,7 +5395,7 @@ msgstr ""
msgid "Single or three-phase" msgid "Single or three-phase"
msgstr "" 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/dcim/ui/panels.py:109
#: netbox/templates/dcim/virtualdevicecontext.html:30 #: netbox/templates/dcim/virtualdevicecontext.html:30
#: netbox/templates/virtualization/virtualmachine.html:56 #: netbox/templates/virtualization/virtualmachine.html:56
@@ -5401,7 +5406,7 @@ msgstr ""
msgid "IPv4 address with mask, e.g. 1.2.3.4/24" msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
msgstr "" 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/dcim/ui/panels.py:114
#: netbox/templates/dcim/virtualdevicecontext.html:41 #: netbox/templates/dcim/virtualdevicecontext.html:41
#: netbox/templates/virtualization/virtualmachine.html:72 #: netbox/templates/virtualization/virtualmachine.html:72
@@ -5604,7 +5609,7 @@ msgstr ""
msgid "Mgmt only" msgid "Mgmt only"
msgstr "" 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/dcim/models/device_components.py:791
#: netbox/templates/dcim/interface.html:155 #: netbox/templates/dcim/interface.html:155
msgid "WWN" msgid "WWN"
@@ -5705,7 +5710,7 @@ msgstr ""
msgid "Rear ports" msgid "Rear ports"
msgstr "" msgstr ""
#: netbox/dcim/forms/mixins.py:158 #: netbox/dcim/forms/mixins.py:155
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"The total number of front port positions ({frontport_count}) must match the " "The total number of front port positions ({frontport_count}) must match the "
@@ -5777,35 +5782,35 @@ msgid ""
"replaced with the position value when creating a new module." "replaced with the position value when creating a new module."
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1252 #: netbox/dcim/forms/model_forms.py:1231
msgid "Console port template" msgid "Console port template"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1260 #: netbox/dcim/forms/model_forms.py:1239
msgid "Console server port template" msgid "Console server port template"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1268 #: netbox/dcim/forms/model_forms.py:1247
msgid "Front port template" msgid "Front port template"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1276 #: netbox/dcim/forms/model_forms.py:1255
msgid "Interface template" msgid "Interface template"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1284 #: netbox/dcim/forms/model_forms.py:1263
msgid "Power outlet template" msgid "Power outlet template"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1292 #: netbox/dcim/forms/model_forms.py:1271
msgid "Power port template" msgid "Power port template"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1300 #: netbox/dcim/forms/model_forms.py:1279
msgid "Rear port template" msgid "Rear port template"
msgstr "" 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/dcim/tables/connections.py:27
#: netbox/templates/dcim/consoleport.html:17 #: netbox/templates/dcim/consoleport.html:17
#: netbox/templates/dcim/consoleserverport.html:73 #: netbox/templates/dcim/consoleserverport.html:73
@@ -5813,14 +5818,14 @@ msgstr ""
msgid "Console Port" msgid "Console Port"
msgstr "" 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/consoleport.html:73
#: netbox/templates/dcim/consoleserverport.html:17 #: netbox/templates/dcim/consoleserverport.html:17
#: netbox/templates/dcim/frontport.html:106 #: netbox/templates/dcim/frontport.html:106
msgid "Console Server Port" msgid "Console Server Port"
msgstr "" 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/circuits/inc/circuit_termination_fields.html:53
#: netbox/templates/dcim/consoleport.html:76 #: netbox/templates/dcim/consoleport.html:76
#: netbox/templates/dcim/consoleserverport.html:76 #: netbox/templates/dcim/consoleserverport.html:76
@@ -5832,7 +5837,7 @@ msgstr ""
msgid "Front Port" msgid "Front Port"
msgstr "" 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/circuits/inc/circuit_termination_fields.html:54
#: netbox/templates/dcim/consoleport.html:79 #: netbox/templates/dcim/consoleport.html:79
#: netbox/templates/dcim/consoleserverport.html:79 #: netbox/templates/dcim/consoleserverport.html:79
@@ -5844,80 +5849,80 @@ msgstr ""
msgid "Rear Port" msgid "Rear Port"
msgstr "" 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/dcim/tables/connections.py:46 netbox/dcim/tables/devices.py:526
#: netbox/templates/dcim/poweroutlet.html:58 #: netbox/templates/dcim/poweroutlet.html:58
#: netbox/templates/dcim/powerport.html:17 #: netbox/templates/dcim/powerport.html:17
msgid "Power Port" msgid "Power Port"
msgstr "" 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/poweroutlet.html:17
#: netbox/templates/dcim/powerport.html:77 #: netbox/templates/dcim/powerport.html:77
msgid "Power Outlet" msgid "Power Outlet"
msgstr "" 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" msgid "Component Assignment"
msgstr "" 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." msgid "An InventoryItem can only be assigned to a single component."
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1500 #: netbox/dcim/forms/model_forms.py:1479
msgid "LAG interface" msgid "LAG interface"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1523 #: netbox/dcim/forms/model_forms.py:1502
msgid "Filter VLANs available for assignment by group." msgid "Filter VLANs available for assignment by group."
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1711 #: netbox/dcim/forms/model_forms.py:1671
msgid "Child Device" msgid "Child Device"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1712 #: netbox/dcim/forms/model_forms.py:1672
msgid "" msgid ""
"Child devices must first be created and assigned to the site and rack of the " "Child devices must first be created and assigned to the site and rack of the "
"parent device." "parent device."
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1754 #: netbox/dcim/forms/model_forms.py:1714
msgid "Console port" msgid "Console port"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1762 #: netbox/dcim/forms/model_forms.py:1722
msgid "Console server port" msgid "Console server port"
msgstr "" 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" msgid "Front port"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1786 #: netbox/dcim/forms/model_forms.py:1746
msgid "Power outlet" msgid "Power outlet"
msgstr "" 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" msgid "Rear port"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1808 #: netbox/dcim/forms/model_forms.py:1768
#: netbox/templates/dcim/inventoryitem.html:17 #: netbox/templates/dcim/inventoryitem.html:17
msgid "Inventory Item" msgid "Inventory Item"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1877 #: netbox/dcim/forms/model_forms.py:1837
#: netbox/templates/dcim/inventoryitemrole.html:15 #: netbox/templates/dcim/inventoryitemrole.html:15
msgid "Inventory Item Role" msgid "Inventory Item Role"
msgstr "" msgstr ""
#: netbox/dcim/forms/model_forms.py:1947 #: netbox/dcim/forms/model_forms.py:1907
msgid "VM Interface" msgid "VM Interface"
msgstr "" 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/ipam/forms/model_forms.py:323 netbox/ipam/tables/vlans.py:171
#: netbox/templates/virtualization/virtualdisk.html:21 #: netbox/templates/virtualization/virtualdisk.html:21
#: netbox/templates/virtualization/virtualmachine.html:12 #: netbox/templates/virtualization/virtualmachine.html:12
@@ -5934,7 +5939,7 @@ msgstr ""
msgid "Virtual Machine" msgid "Virtual Machine"
msgstr "" 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." msgid "A MAC address can only be assigned to a single object."
msgstr "" msgstr ""
@@ -7674,7 +7679,7 @@ msgstr ""
#: netbox/dcim/tables/devices.py:252 netbox/dcim/tables/devices.py:1125 #: 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/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/netbox/navigation/menu.py:95 netbox/netbox/navigation/menu.py:259
#: netbox/templates/dcim/buttons/bulk_add_components.html:38 #: netbox/templates/dcim/buttons/bulk_add_components.html:38
#: netbox/templates/dcim/device/base.html:37 #: netbox/templates/dcim/device/base.html:37
@@ -7721,7 +7726,7 @@ msgstr ""
#: netbox/dcim/tables/devices.py:333 netbox/dcim/tables/devicetypes.py:52 #: 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/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/buttons/bulk_add_components.html:66
#: netbox/templates/dcim/device/base.html:52 #: netbox/templates/dcim/device/base.html:52
#: netbox/templates/dcim/devicetype/base.html:49 #: netbox/templates/dcim/devicetype/base.html:49
@@ -7855,7 +7860,7 @@ msgid "Device Count"
msgstr "" msgstr ""
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:1352 #: 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/netbox/navigation/menu.py:98
#: netbox/templates/dcim/buttons/bulk_add_components.html:10 #: netbox/templates/dcim/buttons/bulk_add_components.html:10
#: netbox/templates/dcim/device/base.html:25 #: netbox/templates/dcim/device/base.html:25
@@ -7866,7 +7871,7 @@ msgid "Console Ports"
msgstr "" msgstr ""
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:1367 #: 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/netbox/navigation/menu.py:99
#: netbox/templates/dcim/buttons/bulk_add_components.html:17 #: netbox/templates/dcim/buttons/bulk_add_components.html:17
#: netbox/templates/dcim/device/base.html:28 #: netbox/templates/dcim/device/base.html:28
@@ -7877,7 +7882,7 @@ msgid "Console Server Ports"
msgstr "" msgstr ""
#: netbox/dcim/tables/devicetypes.py:125 netbox/dcim/views.py:1382 #: 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/netbox/navigation/menu.py:100
#: netbox/templates/dcim/buttons/bulk_add_components.html:24 #: netbox/templates/dcim/buttons/bulk_add_components.html:24
#: netbox/templates/dcim/device/base.html:31 #: netbox/templates/dcim/device/base.html:31
@@ -7888,7 +7893,7 @@ msgid "Power Ports"
msgstr "" msgstr ""
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1397 #: 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/netbox/navigation/menu.py:101
#: netbox/templates/dcim/buttons/bulk_add_components.html:31 #: netbox/templates/dcim/buttons/bulk_add_components.html:31
#: netbox/templates/dcim/device/base.html:34 #: netbox/templates/dcim/device/base.html:34
@@ -7899,7 +7904,7 @@ msgid "Power Outlets"
msgstr "" msgstr ""
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1427 #: 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/netbox/navigation/menu.py:96
#: netbox/templates/dcim/device/base.html:40 #: netbox/templates/dcim/device/base.html:40
#: netbox/templates/dcim/devicetype/base.html:37 #: netbox/templates/dcim/devicetype/base.html:37
@@ -7909,7 +7914,7 @@ msgid "Front Ports"
msgstr "" msgstr ""
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1442 #: 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/netbox/navigation/menu.py:97
#: netbox/templates/dcim/buttons/bulk_add_components.html:45 #: netbox/templates/dcim/buttons/bulk_add_components.html:45
#: netbox/templates/dcim/device/base.html:43 #: netbox/templates/dcim/device/base.html:43
@@ -7920,7 +7925,7 @@ msgid "Rear Ports"
msgstr "" msgstr ""
#: netbox/dcim/tables/devicetypes.py:140 netbox/dcim/views.py:1472 #: 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/buttons/bulk_add_components.html:52
#: netbox/templates/dcim/device/base.html:49 #: netbox/templates/dcim/device/base.html:49
#: netbox/templates/dcim/devicetype/base.html:46 #: netbox/templates/dcim/devicetype/base.html:46
@@ -7928,7 +7933,7 @@ msgid "Device Bays"
msgstr "" msgstr ""
#: netbox/dcim/tables/devicetypes.py:143 netbox/dcim/views.py:1457 #: 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/netbox/navigation/menu.py:102
#: netbox/templates/dcim/buttons/bulk_add_components.html:59 #: netbox/templates/dcim/buttons/bulk_add_components.html:59
#: netbox/templates/dcim/device/base.html:46 #: netbox/templates/dcim/device/base.html:46
@@ -8067,13 +8072,13 @@ msgstr ""
msgid "Reservations" msgid "Reservations"
msgstr "" 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/ipam/ipaddress.html:118
#: netbox/templates/virtualization/virtualmachine.html:160 #: netbox/templates/virtualization/virtualmachine.html:160
msgid "Application Services" msgid "Application Services"
msgstr "" 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/extras/forms/model_forms.py:691
#: netbox/templates/extras/configcontext.html:10 #: netbox/templates/extras/configcontext.html:10
#: netbox/virtualization/forms/model_forms.py:225 #: netbox/virtualization/forms/model_forms.py:225
@@ -8081,41 +8086,41 @@ msgstr ""
msgid "Config Context" msgid "Config Context"
msgstr "" msgstr ""
#: netbox/dcim/views.py:2687 netbox/virtualization/views.py:410 #: netbox/dcim/views.py:2688 netbox/virtualization/views.py:410
msgid "Render Config" msgid "Render Config"
msgstr "" 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/netbox/navigation/menu.py:256 netbox/netbox/navigation/menu.py:258
#: netbox/virtualization/views.py:224 #: netbox/virtualization/views.py:224
msgid "Virtual Machines" msgid "Virtual Machines"
msgstr "" msgstr ""
#: netbox/dcim/views.py:3509 #: netbox/dcim/views.py:3510
#, python-brace-format #, python-brace-format
msgid "Installed device {device} in bay {device_bay}." msgid "Installed device {device} in bay {device_bay}."
msgstr "" msgstr ""
#: netbox/dcim/views.py:3550 #: netbox/dcim/views.py:3551
#, python-brace-format #, python-brace-format
msgid "Removed device {device} from bay {device_bay}." msgid "Removed device {device} from bay {device_bay}."
msgstr "" 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" msgid "Children"
msgstr "" msgstr ""
#: netbox/dcim/views.py:4136 #: netbox/dcim/views.py:4137
#, python-brace-format #, python-brace-format
msgid "Added member <a href=\"{url}\">{device}</a>" msgid "Added member <a href=\"{url}\">{device}</a>"
msgstr "" msgstr ""
#: netbox/dcim/views.py:4181 #: netbox/dcim/views.py:4182
#, python-brace-format #, python-brace-format
msgid "Unable to remove master device {device} from the virtual chassis." msgid "Unable to remove master device {device} from the virtual chassis."
msgstr "" msgstr ""
#: netbox/dcim/views.py:4192 #: netbox/dcim/views.py:4193
#, python-brace-format #, python-brace-format
msgid "Removed {device} from virtual chassis {chassis}" msgid "Removed {device} from virtual chassis {chassis}"
msgstr "" msgstr ""
@@ -11240,7 +11245,13 @@ msgid ""
"parent object" "parent object"
msgstr "" msgstr ""
#: netbox/ipam/models/ip.py:946 #: netbox/ipam/models/ip.py:947
msgid ""
"Cannot reassign IP address while it is designated as the OOB IP for the "
"parent object"
msgstr ""
#: netbox/ipam/models/ip.py:953
msgid "Only IPv6 addresses can be assigned SLAAC status" msgid "Only IPv6 addresses can be assigned SLAAC status"
msgstr "" msgstr ""
@@ -12489,8 +12500,8 @@ msgstr ""
msgid "Delete Selected" msgid "Delete Selected"
msgstr "" msgstr ""
#: netbox/netbox/plugins/navigation.py:55 #: netbox/netbox/plugins/navigation.py:53
#: netbox/netbox/plugins/navigation.py:88 #: netbox/netbox/plugins/navigation.py:89
msgid "Permissions must be passed as a tuple or list." msgid "Permissions must be passed as a tuple or list."
msgstr "" msgstr ""
@@ -12498,7 +12509,7 @@ msgstr ""
msgid "Buttons must be passed as a tuple or list." msgid "Buttons must be passed as a tuple or list."
msgstr "" msgstr ""
#: netbox/netbox/plugins/navigation.py:92 #: netbox/netbox/plugins/navigation.py:95
msgid "Button color must be a choice within ButtonColorChoices." msgid "Button color must be a choice within ButtonColorChoices."
msgstr "" msgstr ""
@@ -16576,7 +16587,7 @@ msgstr ""
msgid "Missing required value for static query param: '{static_params}'" msgid "Missing required value for static query param: '{static_params}'"
msgstr "" msgstr ""
#: netbox/utilities/forms/widgets/modifiers.py:111 #: netbox/utilities/forms/widgets/modifiers.py:141
msgid "(automatically set)" msgid "(automatically set)"
msgstr "" msgstr ""

View File

@@ -1,6 +1,8 @@
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from utilities.forms.widgets.apiselect import APISelect, APISelectMultiple
__all__ = ( __all__ = (
'FilterModifierWidget', 'FilterModifierWidget',
'MODIFIER_EMPTY_FALSE', 'MODIFIER_EMPTY_FALSE',
@@ -94,9 +96,37 @@ class FilterModifierWidget(forms.Widget):
# to the original widget before rendering # to the original widget before rendering
self.original_widget.attrs.update(self.attrs) 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 # Get context from the original widget
original_context = self.original_widget.get_context(name, value, attrs) 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 # Build our wrapper context
context = super().get_context(name, value, attrs) context = super().get_context(name, value, attrs)
context['widget']['original_widget'] = original_context['widget'] context['widget']['original_widget'] = original_context['widget']

View File

@@ -1,16 +0,0 @@
#!/bin/sh
# TODO: Remove this file in NetBox v4.3
# This script has been maintained to ease transition to the pre-commit tool.
exec 1>&2
EXIT=0
RED='\033[0;31m'
YELLOW='\033[0;33m'
NOCOLOR='\033[0m'
printf "${YELLOW}The pre-commit hook script is obsolete. Please use pre-commit instead:${NOCOLOR}\n"
printf " pip install pre-commit\n"
printf " pre-commit install${NOCOLOR}\n"
exit 1