mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-07 12:36:55 -06:00
Compare commits
2 Commits
20044-elev
...
b0f7024dcb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0f7024dcb | ||
|
|
605c61ef5b |
@@ -15,7 +15,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.4.9
|
||||
placeholder: v4.4.8
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
@@ -27,7 +27,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox Version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.4.9
|
||||
placeholder: v4.4.8
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<a href="https://github.com/netbox-community/netbox/blob/main/LICENSE.txt"><img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg" alt="License" /></a>
|
||||
<a href="https://github.com/netbox-community/netbox/graphs/contributors"><img src="https://img.shields.io/github/contributors/netbox-community/netbox?color=blue" alt="Contributors" /></a>
|
||||
<a href="https://github.com/netbox-community/netbox/stargazers"><img src="https://img.shields.io/github/stars/netbox-community/netbox?style=flat" alt="GitHub stars" /></a>
|
||||
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-16-blue" alt="Languages supported" /></a>
|
||||
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-15-blue" alt="Languages supported" /></a>
|
||||
<a href="https://github.com/netbox-community/netbox/actions/workflows/ci.yml"><img src="https://github.com/netbox-community/netbox/actions/workflows/ci.yml/badge.svg" alt="CI status" /></a>
|
||||
<p>
|
||||
<strong><a href="https://netboxlabs.com/community/">NetBox Community</a></strong> |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "NetBox REST API",
|
||||
"version": "4.4.9",
|
||||
"version": "4.4.8",
|
||||
"license": {
|
||||
"name": "Apache v2 License"
|
||||
}
|
||||
@@ -158511,7 +158511,6 @@
|
||||
"fr",
|
||||
"it",
|
||||
"ja",
|
||||
"lv",
|
||||
"nl",
|
||||
"pl",
|
||||
"pt",
|
||||
@@ -205631,9 +205630,15 @@
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 200
|
||||
},
|
||||
"devicetype_count": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"devicetype_count",
|
||||
"display",
|
||||
"id",
|
||||
"name",
|
||||
|
||||
@@ -1,31 +1,5 @@
|
||||
# NetBox v4.4
|
||||
|
||||
## v4.4.9 (2025-12-23)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#20309](https://github.com/netbox-community/netbox/issues/20309) - Support ASDOT notation for ASN ranges
|
||||
* [#20720](https://github.com/netbox-community/netbox/issues/20720) - Add Latvian translations
|
||||
* [#20900](https://github.com/netbox-community/netbox/issues/20900) - Allow filtering custom choice fields by multiple values in the UI
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#17976](https://github.com/netbox-community/netbox/issues/17976) - Remove `devicetype_count` from nested manufacturer to correct OpenAPI schema
|
||||
* [#20011](https://github.com/netbox-community/netbox/issues/20011) - Provide a clear message when encountering duplicate object IDs during bulk import
|
||||
* [#20114](https://github.com/netbox-community/netbox/issues/20114) - Preserve `parent_bay` during device bulk import when tags are present
|
||||
* [#20491](https://github.com/netbox-community/netbox/issues/20491) - Improve handling of numeric ranges in tests
|
||||
* [#20873](https://github.com/netbox-community/netbox/issues/20873) - Fix `AttributeError` exception triggered by event rules associated with an object that supports file attachments
|
||||
* [#20875](https://github.com/netbox-community/netbox/issues/20875) - Ensure that parent object relations are cached (for filtering) on device/module components during instantiation
|
||||
* [#20876](https://github.com/netbox-community/netbox/issues/20876) - Allow editing an IP address that resides within a range marked as populated
|
||||
* [#20912](https://github.com/netbox-community/netbox/issues/20912) - Fix inconsistent clearing of `module` field on ModuleBay
|
||||
* [#20944](https://github.com/netbox-community/netbox/issues/20944) - Ensure cached scope is updated on child objects when a parent region/site/location is changed
|
||||
* [#20948](https://github.com/netbox-community/netbox/issues/20948) - Handle the deletion of related objects with `on_delete=RESTRICT` the same as `CASCADE`
|
||||
* [#20969](https://github.com/netbox-community/netbox/issues/20969) - Fix querying of front port templates by `rear_port_id`
|
||||
* [#21011](https://github.com/netbox-community/netbox/issues/21011) - Avoid writing to the database when loading active ConfigRevision
|
||||
* [#21032](https://github.com/netbox-community/netbox/issues/21032) - Avoid SQL subquery in RestrictedQuerySet where unnecessary
|
||||
|
||||
---
|
||||
|
||||
## v4.4.8 (2025-12-09)
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -63,20 +63,16 @@ class ConfigRevision(models.Model):
|
||||
return reverse('core:config') # Default config view
|
||||
return reverse('core:configrevision', args=[self.pk])
|
||||
|
||||
def activate(self, update_db=True):
|
||||
def activate(self):
|
||||
"""
|
||||
Cache the configuration data.
|
||||
|
||||
Parameters:
|
||||
update_db: Mark the ConfigRevision as active in the database (default: True)
|
||||
"""
|
||||
cache.set('config', self.data, None)
|
||||
cache.set('config_version', self.pk, None)
|
||||
|
||||
if update_db:
|
||||
# Set all instances of ConfigRevision to false and set this instance to true
|
||||
ConfigRevision.objects.all().update(active=False)
|
||||
ConfigRevision.objects.filter(pk=self.pk).update(active=True)
|
||||
# Set all instances of ConfigRevision to false and set this instance to true
|
||||
ConfigRevision.objects.all().update(active=False)
|
||||
ConfigRevision.objects.filter(pk=self.pk).update(active=True)
|
||||
|
||||
activate.alters_data = True
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ clear_events = Signal()
|
||||
# Object types
|
||||
#
|
||||
|
||||
|
||||
@receiver(post_migrate)
|
||||
def update_object_types(sender, **kwargs):
|
||||
"""
|
||||
@@ -133,7 +134,7 @@ def handle_changed_object(sender, instance, **kwargs):
|
||||
prev_change := ObjectChange.objects.filter(
|
||||
changed_object_type=ContentType.objects.get_for_model(instance),
|
||||
changed_object_id=instance.pk,
|
||||
request_id=request.id
|
||||
request_id=request.id,
|
||||
).first()
|
||||
):
|
||||
prev_change.postchange_data = objectchange.postchange_data
|
||||
@@ -172,9 +173,7 @@ def handle_deleted_object(sender, instance, **kwargs):
|
||||
try:
|
||||
run_validators(instance, validators)
|
||||
except ValidationError as e:
|
||||
raise AbortRequest(
|
||||
_("Deletion is prevented by a protection rule: {message}").format(message=e)
|
||||
)
|
||||
raise AbortRequest(_("Deletion is prevented by a protection rule: {message}").format(message=e))
|
||||
|
||||
# Get the current request, or bail if not set
|
||||
request = current_request.get()
|
||||
@@ -221,7 +220,12 @@ def handle_deleted_object(sender, instance, **kwargs):
|
||||
obj.snapshot() # Ensure the change record includes the "before" state
|
||||
if type(relation) is ManyToManyRel:
|
||||
getattr(obj, related_field_name).remove(instance)
|
||||
elif type(relation) is ManyToOneRel and relation.null and relation.on_delete not in (CASCADE, RESTRICT):
|
||||
elif (
|
||||
type(relation) is ManyToOneRel
|
||||
and relation.null
|
||||
and relation.on_delete is not CASCADE
|
||||
and relation.on_delete is not RESTRICT
|
||||
):
|
||||
setattr(obj, related_field_name, None)
|
||||
obj.save()
|
||||
|
||||
@@ -256,6 +260,7 @@ def clear_events_queue(sender, **kwargs):
|
||||
# DataSource handlers
|
||||
#
|
||||
|
||||
|
||||
@receiver(post_save, sender=DataSource)
|
||||
def enqueue_sync_job(instance, created, **kwargs):
|
||||
"""
|
||||
@@ -267,9 +272,10 @@ def enqueue_sync_job(instance, created, **kwargs):
|
||||
SyncDataSourceJob.enqueue_once(instance, interval=instance.sync_interval)
|
||||
elif not created:
|
||||
# Delete any previously scheduled recurring jobs for this DataSource
|
||||
for job in SyncDataSourceJob.get_jobs(instance).defer('data').filter(
|
||||
interval__isnull=False,
|
||||
status=JobStatusChoices.STATUS_SCHEDULED
|
||||
for job in (
|
||||
SyncDataSourceJob.get_jobs(instance)
|
||||
.defer('data')
|
||||
.filter(interval__isnull=False, status=JobStatusChoices.STATUS_SCHEDULED)
|
||||
):
|
||||
# Call delete() per instance to ensure the associated background task is deleted as well
|
||||
job.delete()
|
||||
|
||||
@@ -350,14 +350,14 @@ class ModuleBaySerializer(NetBoxModelSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module = ModuleSerializer(
|
||||
nested=True,
|
||||
fields=('id', 'url', 'display', 'device', 'module_bay'),
|
||||
fields=('id', 'url', 'display'),
|
||||
required=False,
|
||||
allow_null=True,
|
||||
default=None
|
||||
)
|
||||
installed_module = ModuleSerializer(
|
||||
nested=True,
|
||||
fields=('id', 'url', 'display', 'device', 'module_bay', 'serial', 'description'),
|
||||
fields=('id', 'url', 'display', 'serial', 'description'),
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
|
||||
@@ -20,4 +20,4 @@ class ManufacturerSerializer(NetBoxModelSerializer):
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'devicetype_count', 'inventoryitem_count', 'platform_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description')
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'devicetype_count')
|
||||
|
||||
@@ -875,7 +875,7 @@ class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCo
|
||||
null_value=None
|
||||
)
|
||||
rear_port_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=RearPortTemplate.objects.all()
|
||||
queryset=RearPort.objects.all()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -1222,8 +1222,6 @@ class ModuleBay(ModularComponentModel, TrackingModelMixin, MPTTModel):
|
||||
def save(self, *args, **kwargs):
|
||||
if self.module:
|
||||
self.parent = self.module.module_bay
|
||||
else:
|
||||
self.parent = None
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -957,11 +957,6 @@ class Device(
|
||||
if cf_defaults := CustomField.objects.get_defaults_for_model(model):
|
||||
for component in components:
|
||||
component.custom_field_data = cf_defaults
|
||||
# Set denormalized references
|
||||
for component in components:
|
||||
component._site = self.site
|
||||
component._location = self.location
|
||||
component._rack = self.rack
|
||||
components = model.objects.bulk_create(components)
|
||||
# Prefetch related objects to minimize queries needed during post_save
|
||||
prefetch_fields = get_prefetchable_fields(model)
|
||||
|
||||
@@ -315,12 +315,6 @@ class Module(PrimaryModel, ConfigContextModel):
|
||||
for component in create_instances:
|
||||
component.custom_field_data = cf_defaults
|
||||
|
||||
# Set denormalized references
|
||||
for component in create_instances:
|
||||
component._site = self.device.site
|
||||
component._location = self.device.location
|
||||
component._rack = self.device.rack
|
||||
|
||||
if component_model is not ModuleBay:
|
||||
component_model.objects.bulk_create(create_instances)
|
||||
# Emit the post_save signal for each newly created object
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import logging
|
||||
|
||||
from django.db.models.signals import post_delete, post_save
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from dcim.choices import CableEndChoices, LinkStatusChoices
|
||||
from ipam.models import Prefix
|
||||
from virtualization.models import Cluster, VMInterface
|
||||
from wireless.models import WirelessLAN
|
||||
from virtualization.models import VMInterface
|
||||
from .models import (
|
||||
Cable, CablePath, CableTermination, ConsolePort, ConsoleServerPort, Device, DeviceBay, FrontPort, Interface,
|
||||
InventoryItem, Location, ModuleBay, PathEndpoint, PowerOutlet, PowerPanel, PowerPort, Rack, RearPort, Site,
|
||||
InventoryItem, ModuleBay, PathEndpoint, PowerOutlet, PowerPanel, PowerPort, Rack, RearPort, Location,
|
||||
VirtualChassis,
|
||||
)
|
||||
from .models.cables import trace_paths
|
||||
@@ -46,9 +44,6 @@ def handle_location_site_change(instance, created, **kwargs):
|
||||
Device.objects.filter(location__in=locations).update(site=instance.site)
|
||||
PowerPanel.objects.filter(location__in=locations).update(site=instance.site)
|
||||
CableTermination.objects.filter(_location__in=locations).update(_site=instance.site)
|
||||
# Update component models for devices in these locations
|
||||
for model in COMPONENT_MODELS:
|
||||
model.objects.filter(device__location__in=locations).update(_site=instance.site)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Rack)
|
||||
@@ -58,12 +53,6 @@ def handle_rack_site_change(instance, created, **kwargs):
|
||||
"""
|
||||
if not created:
|
||||
Device.objects.filter(rack=instance).update(site=instance.site, location=instance.location)
|
||||
# Update component models for devices in this rack
|
||||
for model in COMPONENT_MODELS:
|
||||
model.objects.filter(device__rack=instance).update(
|
||||
_site=instance.site,
|
||||
_location=instance.location,
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Device)
|
||||
@@ -182,40 +171,3 @@ def update_mac_address_interface(instance, created, raw, **kwargs):
|
||||
if created and not raw and instance.primary_mac_address:
|
||||
instance.primary_mac_address.assigned_object = instance
|
||||
instance.primary_mac_address.save()
|
||||
|
||||
|
||||
@receiver(post_save, sender=Location)
|
||||
@receiver(post_save, sender=Site)
|
||||
def sync_cached_scope_fields(instance, created, **kwargs):
|
||||
"""
|
||||
Rebuild cached scope fields for all CachedScopeMixin-based models
|
||||
affected by a change in a Region, SiteGroup, Site, or Location.
|
||||
|
||||
This method is safe to run for objects created in the past and does
|
||||
not rely on incremental updates. Cached fields are recomputed from
|
||||
authoritative relationships.
|
||||
"""
|
||||
if created:
|
||||
return
|
||||
|
||||
if isinstance(instance, Location):
|
||||
filters = {'_location': instance}
|
||||
elif isinstance(instance, Site):
|
||||
filters = {'_site': instance}
|
||||
else:
|
||||
return
|
||||
|
||||
# These models are explicitly listed because they all subclass CachedScopeMixin
|
||||
# and therefore require their cached scope fields to be recomputed.
|
||||
for model in (Prefix, Cluster, WirelessLAN):
|
||||
qs = model.objects.filter(**filters)
|
||||
|
||||
for obj in qs.only('id'):
|
||||
# Recompute cache using the same logic as save()
|
||||
obj.cache_related_objects()
|
||||
obj.save(update_fields=[
|
||||
'_location',
|
||||
'_site',
|
||||
'_site_group',
|
||||
'_region',
|
||||
])
|
||||
|
||||
@@ -531,7 +531,7 @@ class RackReservationTest(APIViewTestCases.APIViewTestCase):
|
||||
|
||||
class ManufacturerTest(APIViewTestCases.APIViewTestCase):
|
||||
model = Manufacturer
|
||||
brief_fields = ['description', 'display', 'id', 'name', 'slug', 'url']
|
||||
brief_fields = ['description', 'devicetype_count', 'display', 'id', 'name', 'slug', 'url']
|
||||
create_data = [
|
||||
{
|
||||
'name': 'Manufacturer 4',
|
||||
|
||||
@@ -841,32 +841,6 @@ class ModuleBayTestCase(TestCase):
|
||||
nested_bay = module.modulebays.get(name='SFP A-21')
|
||||
self.assertEqual(nested_bay.label, 'A-21')
|
||||
|
||||
@tag('regression') # #20912
|
||||
def test_module_bay_parent_cleared_when_module_removed(self):
|
||||
"""Test that the parent field is properly cleared when a module bay's module assignment is removed"""
|
||||
device = Device.objects.first()
|
||||
manufacturer = Manufacturer.objects.first()
|
||||
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Test Module Type')
|
||||
bay1 = ModuleBay.objects.create(device=device, name='Test Bay 1')
|
||||
bay2 = ModuleBay.objects.create(device=device, name='Test Bay 2')
|
||||
|
||||
# Install a module in bay1
|
||||
module1 = Module.objects.create(device=device, module_bay=bay1, module_type=module_type)
|
||||
|
||||
# Assign bay2 to module1 and verify parent is now set to bay1 (module1's bay)
|
||||
bay2.module = module1
|
||||
bay2.save()
|
||||
bay2.refresh_from_db()
|
||||
self.assertEqual(bay2.parent, bay1)
|
||||
self.assertEqual(bay2.module, module1)
|
||||
|
||||
# Clear the module assignment (return bay2 to device level) Verify parent is cleared
|
||||
bay2.module = None
|
||||
bay2.save()
|
||||
bay2.refresh_from_db()
|
||||
self.assertIsNone(bay2.parent)
|
||||
self.assertIsNone(bay2.module)
|
||||
|
||||
|
||||
class CableTestCase(TestCase):
|
||||
|
||||
|
||||
@@ -2322,32 +2322,6 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
url = reverse('dcim:device_inventory', kwargs={'pk': device.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_bulk_import_duplicate_ids_error_message(self):
|
||||
device = Device.objects.first()
|
||||
csv_data = (
|
||||
"id,role",
|
||||
f"{device.pk},Device Role 1",
|
||||
f"{device.pk},Device Role 2",
|
||||
)
|
||||
|
||||
self.add_permissions('dcim.add_device', 'dcim.change_device')
|
||||
response = self.client.post(
|
||||
self._get_url('bulk_import'),
|
||||
{
|
||||
'data': '\n'.join(csv_data),
|
||||
'format': ImportFormatChoices.CSV,
|
||||
'csv_delimiter': CSVDelimiterChoices.AUTO,
|
||||
},
|
||||
follow=True
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(
|
||||
f'Duplicate objects found: Device with ID(s) {device.pk} appears multiple times',
|
||||
response.content.decode('utf-8')
|
||||
)
|
||||
|
||||
|
||||
class ModuleTestCase(
|
||||
# Module does not support bulk renaming (no name field) or
|
||||
|
||||
@@ -2454,12 +2454,11 @@ class DeviceBulkImportView(generic.BulkImportView):
|
||||
model_form = forms.DeviceImportForm
|
||||
|
||||
def save_object(self, object_form, request):
|
||||
parent_bay = getattr(object_form.instance, 'parent_bay', None)
|
||||
obj = object_form.save()
|
||||
|
||||
# For child devices, save the reverse relation to the parent device bay
|
||||
if parent_bay:
|
||||
device_bay = parent_bay
|
||||
if getattr(obj, 'parent_bay', None):
|
||||
device_bay = obj.parent_bay
|
||||
device_bay.installed_device = obj
|
||||
device_bay.save()
|
||||
|
||||
|
||||
@@ -449,14 +449,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
return model.objects.filter(pk__in=value)
|
||||
return value
|
||||
|
||||
def to_form_field(
|
||||
self,
|
||||
set_initial=True,
|
||||
enforce_required=True,
|
||||
enforce_visibility=True,
|
||||
for_csv_import=False,
|
||||
for_filterset_form=False,
|
||||
):
|
||||
def to_form_field(self, set_initial=True, enforce_required=True, enforce_visibility=True, for_csv_import=False):
|
||||
"""
|
||||
Return a form field suitable for setting a CustomField's value for an object.
|
||||
|
||||
@@ -464,7 +457,6 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
enforce_required: Honor the value of CustomField.required. Set to False for filtering/bulk editing.
|
||||
enforce_visibility: Honor the value of CustomField.ui_visible. Set to False for filtering.
|
||||
for_csv_import: Return a form field suitable for bulk import of objects in CSV format.
|
||||
for_filterset_form: Return a form field suitable for use in a FilterSet form.
|
||||
"""
|
||||
initial = self.default if set_initial else None
|
||||
required = self.required if enforce_required else False
|
||||
@@ -527,7 +519,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
field_class = CSVMultipleChoiceField
|
||||
field = field_class(choices=choices, required=required, initial=initial)
|
||||
else:
|
||||
if self.type == CustomFieldTypeChoices.TYPE_SELECT and not for_filterset_form:
|
||||
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||
field_class = DynamicChoiceField
|
||||
widget_class = APISelect
|
||||
else:
|
||||
|
||||
@@ -16,7 +16,6 @@ __all__ = (
|
||||
# BGP ASN bounds
|
||||
BGP_ASN_MIN = 1
|
||||
BGP_ASN_MAX = 2**32 - 1
|
||||
BGP_ASN_ASDOT_BASE = 2**16
|
||||
|
||||
|
||||
class BaseIPField(models.Field):
|
||||
@@ -127,16 +126,3 @@ class ASNField(models.BigIntegerField):
|
||||
}
|
||||
defaults.update(**kwargs)
|
||||
return super().formfield(**defaults)
|
||||
|
||||
@staticmethod
|
||||
def to_asdot(value) -> str:
|
||||
"""
|
||||
Return ASDOT notation for AS numbers greater than 16 bits.
|
||||
"""
|
||||
if value is None:
|
||||
return ''
|
||||
|
||||
if value >= BGP_ASN_ASDOT_BASE:
|
||||
hi, lo = divmod(value, BGP_ASN_ASDOT_BASE)
|
||||
return f'{hi}.{lo}'
|
||||
return str(value)
|
||||
|
||||
@@ -230,6 +230,10 @@ class PrefixImportForm(ScopedImportForm, NetBoxModelImportForm):
|
||||
query |= Q(**{
|
||||
f"site__{self.fields['vlan_site'].to_field_name}": vlan_site
|
||||
})
|
||||
# Don't Forget to include VLANs without a site in the filter
|
||||
query |= Q(**{
|
||||
f"site__{self.fields['vlan_site'].to_field_name}__isnull": True
|
||||
})
|
||||
|
||||
if vlan_group:
|
||||
query &= Q(**{
|
||||
|
||||
@@ -55,6 +55,13 @@ class ASNRange(OrganizationalModel):
|
||||
def __str__(self):
|
||||
return f'{self.name} ({self.range_as_string()})'
|
||||
|
||||
@property
|
||||
def range(self):
|
||||
return range(self.start, self.end + 1)
|
||||
|
||||
def range_as_string(self):
|
||||
return f'{self.start}-{self.end}'
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
@@ -65,45 +72,7 @@ class ASNRange(OrganizationalModel):
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def range(self):
|
||||
"""
|
||||
Return a range of integers representing the ASN range.
|
||||
"""
|
||||
return range(self.start, self.end + 1)
|
||||
|
||||
@property
|
||||
def start_asdot(self):
|
||||
"""
|
||||
Return ASDOT notation for AS numbers greater than 16 bits.
|
||||
"""
|
||||
return ASNField.to_asdot(self.start)
|
||||
|
||||
@property
|
||||
def end_asdot(self):
|
||||
"""
|
||||
Return ASDOT notation for AS numbers greater than 16 bits.
|
||||
"""
|
||||
return ASNField.to_asdot(self.end)
|
||||
|
||||
def range_as_string(self):
|
||||
"""
|
||||
Return a string representation of the ASN range.
|
||||
"""
|
||||
return f'{self.start}-{self.end}'
|
||||
|
||||
def range_as_string_with_asdot(self):
|
||||
"""
|
||||
Return a string representation of the ASN range, including ASDOT notation.
|
||||
"""
|
||||
if self.end >= 65536:
|
||||
return f'{self.range_as_string()} ({self.start_asdot}-{self.end_asdot})'
|
||||
return self.range_as_string()
|
||||
|
||||
def get_child_asns(self):
|
||||
"""
|
||||
Return all child ASNs (ASNs within the range).
|
||||
"""
|
||||
return ASN.objects.filter(
|
||||
asn__gte=self.start,
|
||||
asn__lte=self.end
|
||||
@@ -162,20 +131,20 @@ class ASN(ContactsMixin, PrimaryModel):
|
||||
"""
|
||||
Return ASDOT notation for AS numbers greater than 16 bits.
|
||||
"""
|
||||
return ASNField.to_asdot(self.asn)
|
||||
if self.asn > 65535:
|
||||
return f'{self.asn // 65536}.{self.asn % 65536}'
|
||||
return self.asn
|
||||
|
||||
@property
|
||||
def asn_with_asdot(self):
|
||||
"""
|
||||
Return both plain and ASDOT notation, where applicable.
|
||||
"""
|
||||
if self.asn >= 65536:
|
||||
return f'{self.asn} ({self.asn_asdot})'
|
||||
return str(self.asn)
|
||||
if self.asn > 65535:
|
||||
return f'{self.asn} ({self.asn // 65536}.{self.asn % 65536})'
|
||||
else:
|
||||
return self.asn
|
||||
|
||||
@property
|
||||
def prefixed_name(self):
|
||||
"""
|
||||
Return the ASN with ASDOT notation prefixed with "AS".
|
||||
"""
|
||||
return f'AS{self.asn_with_asdot}'
|
||||
|
||||
@@ -910,13 +910,13 @@ class IPAddress(ContactsMixin, PrimaryModel):
|
||||
})
|
||||
|
||||
# Disallow the creation of IPAddresses within an IPRange with mark_populated=True
|
||||
parent_range_qs = IPRange.objects.filter(
|
||||
parent_range = IPRange.objects.filter(
|
||||
start_address__lte=self.address,
|
||||
end_address__gte=self.address,
|
||||
vrf=self.vrf,
|
||||
mark_populated=True
|
||||
)
|
||||
if not self.pk and (parent_range := parent_range_qs.first()):
|
||||
).first()
|
||||
if parent_range:
|
||||
raise ValidationError({
|
||||
'address': _(
|
||||
"Cannot create IP address {ip} inside range {range}."
|
||||
|
||||
@@ -20,16 +20,6 @@ class ASNRangeTable(TenancyColumnsMixin, NetBoxTable):
|
||||
verbose_name=_('RIR'),
|
||||
linkify=True
|
||||
)
|
||||
start_asdot = tables.Column(
|
||||
accessor=tables.A('start_asdot'),
|
||||
order_by=tables.A('start'),
|
||||
verbose_name=_('Start (ASDOT)')
|
||||
)
|
||||
end_asdot = tables.Column(
|
||||
accessor=tables.A('end_asdot'),
|
||||
order_by=tables.A('end'),
|
||||
verbose_name=_('End (ASDOT)')
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:asnrange_list'
|
||||
)
|
||||
@@ -40,8 +30,8 @@ class ASNRangeTable(TenancyColumnsMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ASNRange
|
||||
fields = (
|
||||
'pk', 'name', 'slug', 'rir', 'start', 'start_asdot', 'end', 'end_asdot', 'asn_count', 'tenant',
|
||||
'tenant_group', 'description', 'tags', 'created', 'last_updated', 'actions',
|
||||
'pk', 'name', 'slug', 'rir', 'start', 'end', 'asn_count', 'tenant', 'tenant_group', 'description', 'tags',
|
||||
'created', 'last_updated', 'actions',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'rir', 'start', 'end', 'tenant', 'asn_count', 'description')
|
||||
|
||||
|
||||
@@ -1071,17 +1071,14 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
||||
{
|
||||
'name': 'VLAN Group 4',
|
||||
'slug': 'vlan-group-4',
|
||||
'vid_ranges': [[1, 4094]]
|
||||
},
|
||||
{
|
||||
'name': 'VLAN Group 5',
|
||||
'slug': 'vlan-group-5',
|
||||
'vid_ranges': [[1, 4094]]
|
||||
},
|
||||
{
|
||||
'name': 'VLAN Group 6',
|
||||
'slug': 'vlan-group-6',
|
||||
'vid_ranges': [[1, 4094]]
|
||||
},
|
||||
]
|
||||
bulk_update_data = {
|
||||
|
||||
@@ -564,82 +564,6 @@ vlan: 102
|
||||
self.assertEqual(prefix.vlan.vid, 102)
|
||||
self.assertEqual(prefix.scope, site)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_prefix_import_with_vlan_site_multiple_vlans_same_vid(self):
|
||||
"""
|
||||
Test import when multiple VLANs exist with the same vid but different sites.
|
||||
Ref: #20560
|
||||
"""
|
||||
site1 = Site.objects.get(name='Site 1')
|
||||
site2 = Site.objects.get(name='Site 2')
|
||||
|
||||
# Create VLANs with the same vid but different sites
|
||||
vlan1 = VLAN.objects.create(vid=1, name='VLAN1-Site1', site=site1)
|
||||
VLAN.objects.create(vid=1, name='VLAN1-Site2', site=site2) # Create ambiguity
|
||||
|
||||
# Import prefix with vlan_site specified
|
||||
IMPORT_DATA = f"""
|
||||
prefix: 10.11.0.0/22
|
||||
status: active
|
||||
scope_type: dcim.site
|
||||
scope_id: {site1.pk}
|
||||
vlan_site: {site1.name}
|
||||
vlan: 1
|
||||
description: LOC02-MGMT
|
||||
"""
|
||||
|
||||
# Add all required permissions to the test user
|
||||
self.add_permissions('ipam.view_prefix', 'ipam.add_prefix')
|
||||
|
||||
form_data = {
|
||||
'data': IMPORT_DATA,
|
||||
'format': 'yaml'
|
||||
}
|
||||
response = self.client.post(reverse('ipam:prefix_bulk_import'), data=form_data, follow=True)
|
||||
self.assertHttpStatus(response, 200)
|
||||
|
||||
# Verify the prefix was created with the correct VLAN
|
||||
prefix = Prefix.objects.get(prefix='10.11.0.0/22')
|
||||
self.assertEqual(prefix.vlan, vlan1)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_prefix_import_with_vlan_site_and_global_vlan(self):
|
||||
"""
|
||||
Test import when a global VLAN (no site) and site-specific VLAN exist with same vid.
|
||||
When vlan_site is specified, should prefer the site-specific VLAN.
|
||||
Ref: #20560
|
||||
"""
|
||||
site1 = Site.objects.get(name='Site 1')
|
||||
|
||||
# Create a global VLAN (no site) and a site-specific VLAN with the same vid
|
||||
VLAN.objects.create(vid=10, name='VLAN10-Global', site=None) # Create ambiguity
|
||||
vlan_site = VLAN.objects.create(vid=10, name='VLAN10-Site1', site=site1)
|
||||
|
||||
# Import prefix with vlan_site specified
|
||||
IMPORT_DATA = f"""
|
||||
prefix: 10.12.0.0/22
|
||||
status: active
|
||||
scope_type: dcim.site
|
||||
scope_id: {site1.pk}
|
||||
vlan_site: {site1.name}
|
||||
vlan: 10
|
||||
description: Test Site-Specific VLAN
|
||||
"""
|
||||
|
||||
# Add all required permissions to the test user
|
||||
self.add_permissions('ipam.view_prefix', 'ipam.add_prefix')
|
||||
|
||||
form_data = {
|
||||
'data': IMPORT_DATA,
|
||||
'format': 'yaml'
|
||||
}
|
||||
response = self.client.post(reverse('ipam:prefix_bulk_import'), data=form_data, follow=True)
|
||||
self.assertHttpStatus(response, 200)
|
||||
|
||||
# Verify the prefix was created with the site-specific VLAN (not the global one)
|
||||
prefix = Prefix.objects.get(prefix='10.12.0.0/22')
|
||||
self.assertEqual(prefix.vlan, vlan_site)
|
||||
|
||||
|
||||
class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = IPRange
|
||||
|
||||
@@ -80,21 +80,22 @@ class Config:
|
||||
try:
|
||||
# Enforce the creation date as the ordering parameter
|
||||
revision = ConfigRevision.objects.get(active=True)
|
||||
logger.debug(f"Loaded active configuration revision (#{revision.pk})")
|
||||
logger.debug(f"Loaded active configuration revision #{revision.pk}")
|
||||
except (ConfigRevision.DoesNotExist, ConfigRevision.MultipleObjectsReturned):
|
||||
logger.debug("No active configuration revision found - falling back to most recent")
|
||||
revision = ConfigRevision.objects.order_by('-created').first()
|
||||
if revision is None:
|
||||
logger.debug("No configuration found in database; proceeding with default values")
|
||||
logger.debug("No previous configuration found in database; proceeding with default values")
|
||||
return
|
||||
logger.debug(f"No active configuration revision found; falling back to most recent (#{revision.pk})")
|
||||
logger.debug(f"Using fallback configuration revision #{revision.pk}")
|
||||
except DatabaseError:
|
||||
# The database may not be available yet (e.g. when running a management command)
|
||||
logger.warning("Skipping config initialization (database unavailable)")
|
||||
return
|
||||
|
||||
revision.activate(update_db=False)
|
||||
self._populate_from_cache()
|
||||
revision.activate()
|
||||
logger.debug("Filled cache with data from latest ConfigRevision")
|
||||
self._populate_from_cache()
|
||||
|
||||
|
||||
class ConfigItem:
|
||||
|
||||
@@ -205,6 +205,4 @@ class NetBoxModelFilterSetForm(CustomFieldsMixin, SavedFiltersMixin, forms.Form)
|
||||
)
|
||||
|
||||
def _get_form_field(self, customfield):
|
||||
return customfield.to_form_field(
|
||||
set_initial=False, enforce_required=False, enforce_visibility=False, for_filterset_form=True
|
||||
)
|
||||
return customfield.to_form_field(set_initial=False, enforce_required=False, enforce_visibility=False)
|
||||
|
||||
@@ -827,7 +827,6 @@ LANGUAGES = (
|
||||
('fr', _('French')),
|
||||
('it', _('Italian')),
|
||||
('ja', _('Japanese')),
|
||||
('lv', _('Latvian')),
|
||||
('nl', _('Dutch')),
|
||||
('pl', _('Polish')),
|
||||
('pt', _('Portuguese')),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import logging
|
||||
import re
|
||||
from collections import Counter
|
||||
from copy import deepcopy
|
||||
|
||||
from django.contrib import messages
|
||||
@@ -34,7 +33,6 @@ from utilities.jobs import is_background_request, process_request_as_job
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.query import reapply_model_ordering
|
||||
from utilities.request import safe_for_redirect
|
||||
from utilities.string import title
|
||||
from utilities.tables import get_table_configs
|
||||
from utilities.views import GetReturnURLMixin, get_action_url
|
||||
from .base import BaseMultiObjectView
|
||||
@@ -445,18 +443,6 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
|
||||
# Prefetch objects to be updated, if any
|
||||
prefetch_ids = [int(record['id']) for record in records if record.get('id')]
|
||||
|
||||
# check for duplicate IDs
|
||||
duplicate_pks = [pk for pk, count in Counter(prefetch_ids).items() if count > 1]
|
||||
if duplicate_pks:
|
||||
error_msg = _(
|
||||
"Duplicate objects found: {model} with ID(s) {ids} appears multiple times"
|
||||
).format(
|
||||
model=title(self.queryset.model._meta.verbose_name),
|
||||
ids=', '.join(str(pk) for pk in sorted(duplicate_pks))
|
||||
)
|
||||
raise ValidationError(error_msg)
|
||||
|
||||
prefetched_objects = {
|
||||
obj.pk: obj
|
||||
for obj in self.queryset.model.objects.filter(id__in=prefetch_ids)
|
||||
|
||||
2
netbox/project-static/dist/netbox.css
vendored
2
netbox/project-static/dist/netbox.css
vendored
File diff suppressed because one or more lines are too long
8
netbox/project-static/dist/netbox.js
vendored
8
netbox/project-static/dist/netbox.js
vendored
File diff suppressed because one or more lines are too long
6
netbox/project-static/dist/netbox.js.map
vendored
6
netbox/project-static/dist/netbox.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -27,10 +27,10 @@
|
||||
"bootstrap": "5.3.8",
|
||||
"clipboard": "2.0.11",
|
||||
"flatpickr": "4.6.13",
|
||||
"gridstack": "12.4.1",
|
||||
"gridstack": "12.3.3",
|
||||
"htmx.org": "2.0.8",
|
||||
"query-string": "9.3.1",
|
||||
"sass": "1.97.1",
|
||||
"sass": "1.95.0",
|
||||
"tom-select": "2.4.3",
|
||||
"typeface-inter": "3.18.1",
|
||||
"typeface-roboto-mono": "1.1.13"
|
||||
|
||||
@@ -28,27 +28,13 @@ function updateElements(targetMode: ColorMode): void {
|
||||
}
|
||||
|
||||
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
|
||||
const svg = elevation.firstElementChild ?? null;
|
||||
if (svg !== null && svg.nodeName == 'svg') {
|
||||
const svg = elevation.contentDocument?.querySelector('svg') ?? null;
|
||||
if (svg !== null) {
|
||||
svg.setAttribute(`data-bs-theme`, targetMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color mode to light of elevations after an htmx call.
|
||||
* Pulls current color mode from document
|
||||
*
|
||||
* @param event htmx listener event details. See: https://htmx.org/events/#htmx:afterSwap
|
||||
*/
|
||||
function updateElevations(evt: CustomEvent, ): void {
|
||||
const swappedElement = evt.detail.elt
|
||||
if (swappedElement.nodeName == 'svg') {
|
||||
const currentMode = localStorage.getItem(COLOR_MODE_KEY);
|
||||
swappedElement.setAttribute('data-bs-theme', currentMode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call all functions necessary to update the color mode across the UI.
|
||||
*
|
||||
@@ -129,7 +115,6 @@ function initColorModeToggle(): void {
|
||||
*/
|
||||
export function initColorMode(): void {
|
||||
window.addEventListener('load', defaultColorMode);
|
||||
window.addEventListener('htmx:afterSwap', updateElevations as EventListener); // Uses a custom event from HTMX
|
||||
for (const func of [initColorModeToggle]) {
|
||||
func();
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ form.object-edit {
|
||||
// Make optgroup labels sticky when scrolling through select elements
|
||||
select[multiple] {
|
||||
optgroup {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--bs-body-bg);
|
||||
font-style: normal;
|
||||
|
||||
@@ -2178,10 +2178,10 @@ graphql@16.10.0:
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.10.0.tgz#24c01ae0af6b11ea87bf55694429198aaa8e220c"
|
||||
integrity sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==
|
||||
|
||||
gridstack@12.4.1:
|
||||
version "12.4.1"
|
||||
resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-12.4.1.tgz#4a44511e5da33016e731f00bee279bed550d4ab9"
|
||||
integrity sha512-dYBNVEDw2zwnz0bCDouHk8rMclrMoMn4r6rtNyyWSeYsV3RF8QV2KFRTj4c86T2FsZPr3iQv+/LD/ae29FcpHQ==
|
||||
gridstack@12.3.3:
|
||||
version "12.3.3"
|
||||
resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-12.3.3.tgz#0c4fc3cdf6e1c16e6095bc79ff7240a590d2c200"
|
||||
integrity sha512-Bboi4gj7HXGnx1VFXQNde4Nwi5srdUSuCCnOSszKhFjBs8EtMEWhsKX02BjIKkErq/FjQUkNUbXUYeQaVMQ0jQ==
|
||||
|
||||
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -3190,10 +3190,10 @@ safe-regex-test@^1.1.0:
|
||||
es-errors "^1.3.0"
|
||||
is-regex "^1.2.1"
|
||||
|
||||
sass@1.97.1:
|
||||
version "1.97.1"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.97.1.tgz#f36e492baf8ccdd08d591b58d3d8b53ea35ab905"
|
||||
integrity sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==
|
||||
sass@1.95.0:
|
||||
version "1.95.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.95.0.tgz#3a3a4d4d954313ab50eaf16f6e2548a2f6ec0811"
|
||||
integrity sha512-9QMjhLq+UkOg/4bb8Lt8A+hJZvY3t+9xeZMKSBtBEgxrXA3ed5Ts4NDreUkYgJP1BTmrscQE/xYhf7iShow6lw==
|
||||
dependencies:
|
||||
chokidar "^4.0.0"
|
||||
immutable "^5.0.2"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version: "4.4.9"
|
||||
version: "4.4.8"
|
||||
edition: "Community"
|
||||
published: "2025-12-23"
|
||||
published: "2025-12-09"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Range" %}</th>
|
||||
<td>{{ object.range_as_string_with_asdot }}</td>
|
||||
<td>{{ object.range_as_string }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Tenant" %}</th>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-12-23 05:04+0000\n"
|
||||
"POT-Creation-Date: 2025-12-10 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"
|
||||
@@ -231,7 +231,7 @@ msgstr ""
|
||||
#: netbox/dcim/tables/power.py:93 netbox/dcim/tables/racks.py:125
|
||||
#: netbox/dcim/tables/racks.py:215 netbox/dcim/tables/sites.py:151
|
||||
#: netbox/extras/filtersets.py:662 netbox/ipam/forms/bulk_edit.py:479
|
||||
#: netbox/ipam/forms/bulk_import.py:485 netbox/ipam/forms/filtersets.py:161
|
||||
#: netbox/ipam/forms/bulk_import.py:489 netbox/ipam/forms/filtersets.py:161
|
||||
#: netbox/ipam/forms/filtersets.py:236 netbox/ipam/forms/filtersets.py:457
|
||||
#: netbox/ipam/forms/filtersets.py:552 netbox/ipam/forms/model_forms.py:673
|
||||
#: netbox/ipam/tables/vlans.py:90 netbox/ipam/tables/vlans.py:200
|
||||
@@ -272,8 +272,8 @@ msgid "ASN (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:79 netbox/circuits/forms/filtersets.py:39
|
||||
#: netbox/ipam/forms/model_forms.py:166 netbox/ipam/models/asns.py:137
|
||||
#: netbox/ipam/models/asns.py:154 netbox/ipam/tables/asn.py:51
|
||||
#: netbox/ipam/forms/model_forms.py:166 netbox/ipam/models/asns.py:106
|
||||
#: netbox/ipam/models/asns.py:123 netbox/ipam/tables/asn.py:41
|
||||
#: netbox/templates/ipam/asn.html:20
|
||||
msgid "ASN"
|
||||
msgstr ""
|
||||
@@ -453,8 +453,8 @@ msgstr ""
|
||||
#: netbox/circuits/forms/model_forms.py:43
|
||||
#: netbox/circuits/tables/providers.py:32 netbox/dcim/forms/bulk_edit.py:143
|
||||
#: netbox/dcim/forms/filtersets.py:204 netbox/dcim/forms/model_forms.py:133
|
||||
#: netbox/dcim/tables/sites.py:108 netbox/ipam/models/asns.py:155
|
||||
#: netbox/ipam/tables/asn.py:37 netbox/ipam/views.py:269
|
||||
#: netbox/dcim/tables/sites.py:108 netbox/ipam/models/asns.py:124
|
||||
#: netbox/ipam/tables/asn.py:27 netbox/ipam/views.py:269
|
||||
#: netbox/netbox/navigation/menu.py:179 netbox/netbox/navigation/menu.py:182
|
||||
#: netbox/templates/circuits/provider.html:23
|
||||
msgid "ASNs"
|
||||
@@ -784,8 +784,8 @@ msgstr ""
|
||||
#: netbox/dcim/tables/sites.py:96 netbox/dcim/tables/sites.py:155
|
||||
#: netbox/ipam/forms/bulk_edit.py:240 netbox/ipam/forms/bulk_edit.py:290
|
||||
#: netbox/ipam/forms/bulk_edit.py:343 netbox/ipam/forms/bulk_edit.py:501
|
||||
#: netbox/ipam/forms/bulk_import.py:195 netbox/ipam/forms/bulk_import.py:259
|
||||
#: netbox/ipam/forms/bulk_import.py:295 netbox/ipam/forms/bulk_import.py:506
|
||||
#: netbox/ipam/forms/bulk_import.py:195 netbox/ipam/forms/bulk_import.py:263
|
||||
#: netbox/ipam/forms/bulk_import.py:299 netbox/ipam/forms/bulk_import.py:510
|
||||
#: netbox/ipam/forms/filtersets.py:219 netbox/ipam/forms/filtersets.py:297
|
||||
#: netbox/ipam/forms/filtersets.py:379 netbox/ipam/forms/filtersets.py:564
|
||||
#: netbox/ipam/forms/model_forms.py:512 netbox/ipam/tables/ip.py:184
|
||||
@@ -866,8 +866,8 @@ msgstr ""
|
||||
#: netbox/ipam/forms/bulk_import.py:41 netbox/ipam/forms/bulk_import.py:70
|
||||
#: netbox/ipam/forms/bulk_import.py:98 netbox/ipam/forms/bulk_import.py:118
|
||||
#: netbox/ipam/forms/bulk_import.py:138 netbox/ipam/forms/bulk_import.py:167
|
||||
#: netbox/ipam/forms/bulk_import.py:252 netbox/ipam/forms/bulk_import.py:288
|
||||
#: netbox/ipam/forms/bulk_import.py:468 netbox/ipam/forms/bulk_import.py:499
|
||||
#: netbox/ipam/forms/bulk_import.py:256 netbox/ipam/forms/bulk_import.py:292
|
||||
#: netbox/ipam/forms/bulk_import.py:472 netbox/ipam/forms/bulk_import.py:503
|
||||
#: netbox/ipam/forms/filtersets.py:50 netbox/ipam/forms/filtersets.py:70
|
||||
#: netbox/ipam/forms/filtersets.py:102 netbox/ipam/forms/filtersets.py:123
|
||||
#: netbox/ipam/forms/filtersets.py:146 netbox/ipam/forms/filtersets.py:182
|
||||
@@ -1106,8 +1106,8 @@ msgstr ""
|
||||
#: netbox/extras/filtersets.py:689 netbox/ipam/forms/bulk_edit.py:245
|
||||
#: netbox/ipam/forms/bulk_edit.py:295 netbox/ipam/forms/bulk_edit.py:348
|
||||
#: netbox/ipam/forms/bulk_edit.py:506 netbox/ipam/forms/bulk_import.py:200
|
||||
#: netbox/ipam/forms/bulk_import.py:264 netbox/ipam/forms/bulk_import.py:300
|
||||
#: netbox/ipam/forms/bulk_import.py:511 netbox/ipam/forms/filtersets.py:247
|
||||
#: netbox/ipam/forms/bulk_import.py:268 netbox/ipam/forms/bulk_import.py:304
|
||||
#: netbox/ipam/forms/bulk_import.py:515 netbox/ipam/forms/filtersets.py:247
|
||||
#: netbox/ipam/forms/filtersets.py:305 netbox/ipam/forms/filtersets.py:384
|
||||
#: netbox/ipam/forms/filtersets.py:572 netbox/ipam/forms/model_forms.py:195
|
||||
#: netbox/ipam/forms/model_forms.py:221 netbox/ipam/forms/model_forms.py:260
|
||||
@@ -1160,8 +1160,8 @@ msgstr ""
|
||||
#: netbox/dcim/forms/bulk_import.py:365 netbox/dcim/forms/bulk_import.py:597
|
||||
#: netbox/dcim/forms/bulk_import.py:757 netbox/dcim/forms/bulk_import.py:1250
|
||||
#: netbox/dcim/forms/bulk_import.py:1681 netbox/ipam/forms/bulk_import.py:197
|
||||
#: netbox/ipam/forms/bulk_import.py:261 netbox/ipam/forms/bulk_import.py:297
|
||||
#: netbox/ipam/forms/bulk_import.py:508 netbox/ipam/forms/bulk_import.py:521
|
||||
#: netbox/ipam/forms/bulk_import.py:265 netbox/ipam/forms/bulk_import.py:301
|
||||
#: netbox/ipam/forms/bulk_import.py:512 netbox/ipam/forms/bulk_import.py:525
|
||||
#: netbox/virtualization/forms/bulk_import.py:62
|
||||
#: netbox/virtualization/forms/bulk_import.py:93
|
||||
#: netbox/vpn/forms/bulk_import.py:39 netbox/vpn/forms/bulk_import.py:266
|
||||
@@ -1178,9 +1178,9 @@ msgstr ""
|
||||
#: netbox/dcim/forms/bulk_import.py:1740 netbox/ipam/forms/bulk_import.py:45
|
||||
#: netbox/ipam/forms/bulk_import.py:74 netbox/ipam/forms/bulk_import.py:102
|
||||
#: netbox/ipam/forms/bulk_import.py:122 netbox/ipam/forms/bulk_import.py:142
|
||||
#: netbox/ipam/forms/bulk_import.py:171 netbox/ipam/forms/bulk_import.py:256
|
||||
#: netbox/ipam/forms/bulk_import.py:292 netbox/ipam/forms/bulk_import.py:472
|
||||
#: netbox/ipam/forms/bulk_import.py:503
|
||||
#: netbox/ipam/forms/bulk_import.py:171 netbox/ipam/forms/bulk_import.py:260
|
||||
#: netbox/ipam/forms/bulk_import.py:296 netbox/ipam/forms/bulk_import.py:476
|
||||
#: netbox/ipam/forms/bulk_import.py:507
|
||||
#: netbox/virtualization/forms/bulk_import.py:76
|
||||
#: netbox/virtualization/forms/bulk_import.py:130
|
||||
#: netbox/vpn/forms/bulk_import.py:63 netbox/wireless/forms/bulk_import.py:61
|
||||
@@ -1224,7 +1224,7 @@ msgstr ""
|
||||
#: netbox/dcim/forms/model_forms.py:1571 netbox/dcim/forms/model_forms.py:1738
|
||||
#: netbox/dcim/forms/model_forms.py:1773 netbox/dcim/forms/model_forms.py:1903
|
||||
#: netbox/dcim/tables/connections.py:65 netbox/dcim/tables/devices.py:1169
|
||||
#: netbox/ipam/forms/bulk_import.py:320 netbox/ipam/forms/model_forms.py:291
|
||||
#: netbox/ipam/forms/bulk_import.py:324 netbox/ipam/forms/model_forms.py:291
|
||||
#: netbox/ipam/forms/model_forms.py:300 netbox/ipam/tables/fhrp.py:64
|
||||
#: netbox/ipam/tables/ip.py:330 netbox/ipam/tables/vlans.py:148
|
||||
#: netbox/templates/circuits/inc/circuit_termination_fields.html:52
|
||||
@@ -1389,7 +1389,7 @@ msgstr ""
|
||||
#: netbox/dcim/forms/bulk_import.py:104 netbox/dcim/forms/model_forms.py:126
|
||||
#: netbox/dcim/tables/sites.py:103 netbox/extras/forms/filtersets.py:582
|
||||
#: netbox/ipam/filtersets.py:982 netbox/ipam/forms/bulk_edit.py:488
|
||||
#: netbox/ipam/forms/bulk_import.py:492 netbox/ipam/forms/model_forms.py:571
|
||||
#: netbox/ipam/forms/bulk_import.py:496 netbox/ipam/forms/model_forms.py:571
|
||||
#: netbox/ipam/tables/fhrp.py:67 netbox/ipam/tables/vlans.py:94
|
||||
#: netbox/ipam/tables/vlans.py:205
|
||||
#: netbox/templates/circuits/circuitgroupassignment.html:22
|
||||
@@ -1441,7 +1441,7 @@ msgstr ""
|
||||
#: netbox/dcim/models/device_components.py:517
|
||||
#: netbox/dcim/models/device_components.py:1063
|
||||
#: netbox/dcim/models/device_components.py:1134
|
||||
#: netbox/dcim/models/device_components.py:1282
|
||||
#: netbox/dcim/models/device_components.py:1280
|
||||
#: netbox/dcim/models/devices.py:382 netbox/dcim/models/racks.py:227
|
||||
#: netbox/extras/models/tags.py:29
|
||||
msgid "color"
|
||||
@@ -1469,8 +1469,8 @@ msgstr ""
|
||||
#: netbox/circuits/models/virtual_circuits.py:59 netbox/core/models/data.py:52
|
||||
#: netbox/core/models/jobs.py:95 netbox/dcim/models/cables.py:51
|
||||
#: netbox/dcim/models/device_components.py:488
|
||||
#: netbox/dcim/models/device_components.py:1321
|
||||
#: netbox/dcim/models/devices.py:580 netbox/dcim/models/devices.py:1207
|
||||
#: netbox/dcim/models/device_components.py:1319
|
||||
#: netbox/dcim/models/devices.py:580 netbox/dcim/models/devices.py:1202
|
||||
#: netbox/dcim/models/modules.py:209 netbox/dcim/models/power.py:94
|
||||
#: netbox/dcim/models/racks.py:294 netbox/dcim/models/racks.py:677
|
||||
#: netbox/dcim/models/sites.py:157 netbox/dcim/models/sites.py:281
|
||||
@@ -1604,7 +1604,7 @@ msgstr ""
|
||||
#: netbox/core/models/jobs.py:56
|
||||
#: netbox/dcim/models/device_component_templates.py:44
|
||||
#: netbox/dcim/models/device_components.py:53 netbox/dcim/models/devices.py:524
|
||||
#: netbox/dcim/models/devices.py:1133 netbox/dcim/models/devices.py:1202
|
||||
#: netbox/dcim/models/devices.py:1128 netbox/dcim/models/devices.py:1197
|
||||
#: netbox/dcim/models/modules.py:31 netbox/dcim/models/power.py:38
|
||||
#: netbox/dcim/models/power.py:89 netbox/dcim/models/racks.py:263
|
||||
#: netbox/dcim/models/sites.py:145 netbox/extras/models/configs.py:36
|
||||
@@ -1882,7 +1882,7 @@ msgstr ""
|
||||
#: netbox/dcim/tables/sites.py:40 netbox/dcim/tables/sites.py:74
|
||||
#: netbox/dcim/tables/sites.py:121 netbox/dcim/tables/sites.py:179
|
||||
#: netbox/extras/forms/bulk_import.py:303 netbox/extras/tables/tables.py:706
|
||||
#: netbox/ipam/tables/asn.py:79 netbox/ipam/tables/fhrp.py:34
|
||||
#: netbox/ipam/tables/asn.py:69 netbox/ipam/tables/fhrp.py:34
|
||||
#: netbox/ipam/tables/ip.py:83 netbox/ipam/tables/ip.py:227
|
||||
#: netbox/ipam/tables/ip.py:286 netbox/ipam/tables/ip.py:355
|
||||
#: netbox/ipam/tables/services.py:25 netbox/ipam/tables/services.py:55
|
||||
@@ -1984,7 +1984,7 @@ msgstr ""
|
||||
#: netbox/dcim/tables/devices.py:862 netbox/dcim/tables/devices.py:921
|
||||
#: netbox/dcim/tables/devices.py:989 netbox/dcim/tables/devices.py:1118
|
||||
#: netbox/dcim/tables/modules.py:87 netbox/extras/forms/filtersets.py:389
|
||||
#: netbox/ipam/forms/bulk_import.py:306 netbox/ipam/forms/filtersets.py:626
|
||||
#: netbox/ipam/forms/bulk_import.py:310 netbox/ipam/forms/filtersets.py:626
|
||||
#: netbox/ipam/forms/model_forms.py:334 netbox/ipam/tables/vlans.py:159
|
||||
#: netbox/templates/circuits/virtualcircuittermination.html:56
|
||||
#: netbox/templates/dcim/consoleport.html:20
|
||||
@@ -3185,7 +3185,7 @@ msgstr ""
|
||||
#: netbox/dcim/tables/devices.py:719 netbox/dcim/tables/devices.py:929
|
||||
#: netbox/dcim/tables/devices.py:1016 netbox/dcim/tables/devices.py:1175
|
||||
#: netbox/dcim/tables/sites.py:28 netbox/dcim/tables/sites.py:62
|
||||
#: netbox/dcim/tables/sites.py:147 netbox/ipam/forms/bulk_import.py:578
|
||||
#: netbox/dcim/tables/sites.py:147 netbox/ipam/forms/bulk_import.py:582
|
||||
#: netbox/ipam/forms/model_forms.py:770 netbox/ipam/tables/fhrp.py:59
|
||||
#: netbox/ipam/tables/ip.py:336 netbox/ipam/tables/services.py:45
|
||||
#: netbox/templates/dcim/devicerole.html:34
|
||||
@@ -3817,8 +3817,8 @@ msgstr ""
|
||||
|
||||
#: netbox/dcim/filtersets.py:1197 netbox/dcim/forms/filtersets.py:855
|
||||
#: netbox/dcim/forms/filtersets.py:1483 netbox/dcim/forms/filtersets.py:1699
|
||||
#: netbox/dcim/forms/model_forms.py:1900 netbox/dcim/models/devices.py:1303
|
||||
#: netbox/dcim/models/devices.py:1323 netbox/virtualization/filtersets.py:201
|
||||
#: netbox/dcim/forms/model_forms.py:1900 netbox/dcim/models/devices.py:1298
|
||||
#: netbox/dcim/models/devices.py:1318 netbox/virtualization/filtersets.py:201
|
||||
#: netbox/virtualization/filtersets.py:273
|
||||
#: netbox/virtualization/forms/filtersets.py:178
|
||||
#: netbox/virtualization/forms/filtersets.py:231
|
||||
@@ -3963,7 +3963,7 @@ msgid "Is assigned"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/filtersets.py:1826 netbox/dcim/forms/bulk_import.py:1355
|
||||
#: netbox/ipam/forms/bulk_import.py:334
|
||||
#: netbox/ipam/forms/bulk_import.py:338
|
||||
msgid "Is primary"
|
||||
msgstr ""
|
||||
|
||||
@@ -3991,7 +3991,7 @@ msgstr ""
|
||||
#: netbox/ipam/filtersets.py:579 netbox/ipam/filtersets.py:590
|
||||
#: netbox/ipam/forms/bulk_edit.py:226 netbox/ipam/forms/bulk_edit.py:282
|
||||
#: netbox/ipam/forms/bulk_edit.py:329 netbox/ipam/forms/bulk_import.py:160
|
||||
#: netbox/ipam/forms/bulk_import.py:245 netbox/ipam/forms/bulk_import.py:281
|
||||
#: netbox/ipam/forms/bulk_import.py:249 netbox/ipam/forms/bulk_import.py:285
|
||||
#: netbox/ipam/forms/filtersets.py:69 netbox/ipam/forms/filtersets.py:180
|
||||
#: netbox/ipam/forms/filtersets.py:332 netbox/ipam/forms/model_forms.py:66
|
||||
#: netbox/ipam/forms/model_forms.py:209 netbox/ipam/forms/model_forms.py:257
|
||||
@@ -4856,7 +4856,7 @@ msgid "available options"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/forms/bulk_import.py:138 netbox/dcim/forms/bulk_import.py:633
|
||||
#: netbox/dcim/forms/bulk_import.py:1650 netbox/ipam/forms/bulk_import.py:489
|
||||
#: netbox/dcim/forms/bulk_import.py:1650 netbox/ipam/forms/bulk_import.py:493
|
||||
#: netbox/virtualization/forms/bulk_import.py:69
|
||||
#: netbox/virtualization/forms/bulk_import.py:100
|
||||
msgid "Assigned site"
|
||||
@@ -5168,7 +5168,7 @@ msgid "Assigned Q-in-Q Service VLAN ID (filtered by VLAN group)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/forms/bulk_import.py:1028 netbox/ipam/forms/bulk_import.py:164
|
||||
#: netbox/ipam/forms/bulk_import.py:249 netbox/ipam/forms/bulk_import.py:285
|
||||
#: netbox/ipam/forms/bulk_import.py:253 netbox/ipam/forms/bulk_import.py:289
|
||||
#: netbox/ipam/forms/filtersets.py:210 netbox/ipam/forms/filtersets.py:293
|
||||
#: netbox/ipam/forms/filtersets.py:360
|
||||
#: netbox/virtualization/forms/bulk_import.py:220
|
||||
@@ -5247,11 +5247,11 @@ msgstr ""
|
||||
msgid "Component type must be specified when component name is specified"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/forms/bulk_import.py:1338 netbox/ipam/forms/bulk_import.py:310
|
||||
#: netbox/dcim/forms/bulk_import.py:1338 netbox/ipam/forms/bulk_import.py:314
|
||||
msgid "Parent device of assigned interface (if any)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/forms/bulk_import.py:1341 netbox/ipam/forms/bulk_import.py:313
|
||||
#: netbox/dcim/forms/bulk_import.py:1341 netbox/ipam/forms/bulk_import.py:317
|
||||
#: netbox/virtualization/filtersets.py:259
|
||||
#: netbox/virtualization/filtersets.py:310
|
||||
#: netbox/virtualization/forms/bulk_edit.py:182
|
||||
@@ -5265,12 +5265,12 @@ msgstr ""
|
||||
msgid "Virtual machine"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/forms/bulk_import.py:1345 netbox/ipam/forms/bulk_import.py:317
|
||||
#: netbox/dcim/forms/bulk_import.py:1345 netbox/ipam/forms/bulk_import.py:321
|
||||
msgid "Parent VM of assigned interface (if any)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/forms/bulk_import.py:1352 netbox/ipam/filtersets.py:1035
|
||||
#: netbox/ipam/forms/bulk_import.py:324
|
||||
#: netbox/ipam/forms/bulk_import.py:328
|
||||
msgid "Assigned interface"
|
||||
msgstr ""
|
||||
|
||||
@@ -5654,7 +5654,7 @@ msgstr ""
|
||||
msgid "Please select a {scope_type}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/forms/mixins.py:117 netbox/ipam/forms/bulk_import.py:462
|
||||
#: netbox/dcim/forms/mixins.py:117 netbox/ipam/forms/bulk_import.py:466
|
||||
msgid "Scope type (app & model)"
|
||||
msgstr ""
|
||||
|
||||
@@ -6277,12 +6277,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_component_templates.py:777
|
||||
#: netbox/dcim/models/device_components.py:1342
|
||||
#: netbox/dcim/models/device_components.py:1340
|
||||
msgid "part ID"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_component_templates.py:779
|
||||
#: netbox/dcim/models/device_components.py:1344
|
||||
#: netbox/dcim/models/device_components.py:1342
|
||||
msgid "Manufacturer-assigned part identifier"
|
||||
msgstr ""
|
||||
|
||||
@@ -6404,7 +6404,7 @@ msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:605
|
||||
#: netbox/dcim/tables/devices.py:631 netbox/ipam/forms/bulk_edit.py:521
|
||||
#: netbox/ipam/forms/bulk_import.py:524 netbox/ipam/forms/filtersets.py:587
|
||||
#: netbox/ipam/forms/bulk_import.py:528 netbox/ipam/forms/filtersets.py:587
|
||||
#: netbox/ipam/forms/model_forms.py:694 netbox/ipam/tables/vlans.py:109
|
||||
#: netbox/templates/dcim/interface.html:86 netbox/templates/ipam/vlan.html:77
|
||||
#: netbox/templates/virtualization/vminterface.html:60
|
||||
@@ -6631,83 +6631,83 @@ msgstr ""
|
||||
msgid "A module bay cannot belong to a module installed within it."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1245
|
||||
#: netbox/dcim/models/device_components.py:1243
|
||||
msgid "device bay"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1246
|
||||
#: netbox/dcim/models/device_components.py:1244
|
||||
msgid "device bays"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1253
|
||||
#: netbox/dcim/models/device_components.py:1251
|
||||
#, python-brace-format
|
||||
msgid "This type of device ({device_type}) does not support device bays."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1259
|
||||
#: netbox/dcim/models/device_components.py:1257
|
||||
msgid "Cannot install a device into itself."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1267
|
||||
#: netbox/dcim/models/device_components.py:1265
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Cannot install the specified device; device is already installed in {bay}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1288
|
||||
#: netbox/dcim/models/device_components.py:1286
|
||||
msgid "inventory item role"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1289
|
||||
#: netbox/dcim/models/device_components.py:1287
|
||||
msgid "inventory item roles"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1348
|
||||
#: netbox/dcim/models/device_components.py:1346
|
||||
#: netbox/dcim/models/devices.py:533 netbox/dcim/models/modules.py:217
|
||||
#: netbox/dcim/models/racks.py:310
|
||||
#: netbox/virtualization/models/virtualmachines.py:125
|
||||
msgid "serial number"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1356
|
||||
#: netbox/dcim/models/device_components.py:1354
|
||||
#: netbox/dcim/models/devices.py:541 netbox/dcim/models/modules.py:224
|
||||
#: netbox/dcim/models/racks.py:317
|
||||
msgid "asset tag"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1357
|
||||
#: netbox/dcim/models/device_components.py:1355
|
||||
msgid "A unique tag used to identify this item"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1360
|
||||
#: netbox/dcim/models/device_components.py:1358
|
||||
msgid "discovered"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1362
|
||||
#: netbox/dcim/models/device_components.py:1360
|
||||
msgid "This item was automatically discovered"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1380
|
||||
#: netbox/dcim/models/device_components.py:1378
|
||||
msgid "inventory item"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1381
|
||||
#: netbox/dcim/models/device_components.py:1379
|
||||
msgid "inventory items"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1389
|
||||
#: netbox/dcim/models/device_components.py:1387
|
||||
msgid "Cannot assign self as parent."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1397
|
||||
#: netbox/dcim/models/device_components.py:1395
|
||||
msgid "Parent inventory item does not belong to the same device."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1403
|
||||
#: netbox/dcim/models/device_components.py:1401
|
||||
msgid "Cannot move an inventory item with dependent children"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/device_components.py:1411
|
||||
#: netbox/dcim/models/device_components.py:1409
|
||||
msgid "Cannot assign inventory item to component on another device"
|
||||
msgstr ""
|
||||
|
||||
@@ -6867,12 +6867,12 @@ msgstr ""
|
||||
msgid "rack face"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:598 netbox/dcim/models/devices.py:1223
|
||||
#: netbox/dcim/models/devices.py:598 netbox/dcim/models/devices.py:1218
|
||||
#: netbox/virtualization/models/virtualmachines.py:94
|
||||
msgid "primary IPv4"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:606 netbox/dcim/models/devices.py:1231
|
||||
#: netbox/dcim/models/devices.py:606 netbox/dcim/models/devices.py:1226
|
||||
#: netbox/virtualization/models/virtualmachines.py:102
|
||||
msgid "primary IPv6"
|
||||
msgstr ""
|
||||
@@ -7020,68 +7020,68 @@ msgid ""
|
||||
"is currently designated as its master."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1138
|
||||
#: netbox/dcim/models/devices.py:1133
|
||||
msgid "domain"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1151 netbox/dcim/models/devices.py:1152
|
||||
#: netbox/dcim/models/devices.py:1146 netbox/dcim/models/devices.py:1147
|
||||
msgid "virtual chassis"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1164
|
||||
#: netbox/dcim/models/devices.py:1159
|
||||
#, python-brace-format
|
||||
msgid "The selected master ({master}) is not assigned to this virtual chassis."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1179
|
||||
#: netbox/dcim/models/devices.py:1174
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Unable to delete virtual chassis {self}. There are member interfaces which "
|
||||
"form a cross-chassis LAG interfaces."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1212 netbox/vpn/models/l2vpn.py:42
|
||||
#: netbox/dcim/models/devices.py:1207 netbox/vpn/models/l2vpn.py:42
|
||||
msgid "identifier"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1213
|
||||
#: netbox/dcim/models/devices.py:1208
|
||||
msgid "Numeric identifier unique to the parent device"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1241 netbox/extras/models/customfields.py:231
|
||||
#: netbox/dcim/models/devices.py:1236 netbox/extras/models/customfields.py:231
|
||||
#: netbox/extras/models/models.py:111 netbox/extras/models/models.py:800
|
||||
#: netbox/netbox/models/__init__.py:120 netbox/netbox/models/__init__.py:155
|
||||
msgid "comments"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1257
|
||||
#: netbox/dcim/models/devices.py:1252
|
||||
msgid "virtual device context"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1258
|
||||
#: netbox/dcim/models/devices.py:1253
|
||||
msgid "virtual device contexts"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1287
|
||||
#: netbox/dcim/models/devices.py:1282
|
||||
#, python-brace-format
|
||||
msgid "{ip} is not an IPv{family} address."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1293
|
||||
#: netbox/dcim/models/devices.py:1288
|
||||
msgid "Primary IP address must belong to an interface on the assigned device."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1324
|
||||
#: netbox/dcim/models/devices.py:1319
|
||||
msgid "MAC addresses"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1356
|
||||
#: netbox/dcim/models/devices.py:1351
|
||||
msgid ""
|
||||
"Cannot unassign MAC Address while it is designated as the primary MAC for an "
|
||||
"object"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1360
|
||||
#: netbox/dcim/models/devices.py:1355
|
||||
msgid ""
|
||||
"Cannot reassign MAC Address while it is designated as the primary MAC for an "
|
||||
"object"
|
||||
@@ -7324,8 +7324,8 @@ msgid "Locally-assigned identifier"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/racks.py:305 netbox/ipam/forms/bulk_import.py:204
|
||||
#: netbox/ipam/forms/bulk_import.py:268 netbox/ipam/forms/bulk_import.py:303
|
||||
#: netbox/ipam/forms/bulk_import.py:515
|
||||
#: netbox/ipam/forms/bulk_import.py:272 netbox/ipam/forms/bulk_import.py:307
|
||||
#: netbox/ipam/forms/bulk_import.py:519
|
||||
#: netbox/virtualization/forms/bulk_import.py:123
|
||||
msgid "Functional role"
|
||||
msgstr ""
|
||||
@@ -7576,7 +7576,7 @@ msgid "U Height"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devices.py:210 netbox/dcim/tables/devices.py:1128
|
||||
#: netbox/ipam/forms/bulk_import.py:597 netbox/ipam/forms/model_forms.py:317
|
||||
#: netbox/ipam/forms/bulk_import.py:601 netbox/ipam/forms/model_forms.py:317
|
||||
#: netbox/ipam/forms/model_forms.py:330 netbox/ipam/tables/ip.py:314
|
||||
#: netbox/ipam/tables/ip.py:381 netbox/ipam/tables/ip.py:391
|
||||
#: netbox/ipam/tables/ip.py:414 netbox/templates/ipam/ipaddress.html:11
|
||||
@@ -7942,7 +7942,7 @@ msgstr ""
|
||||
#: netbox/dcim/tables/sites.py:34 netbox/dcim/tables/sites.py:68
|
||||
#: netbox/extras/forms/filtersets.py:424 netbox/extras/forms/model_forms.py:630
|
||||
#: netbox/ipam/forms/bulk_edit.py:134 netbox/ipam/forms/model_forms.py:160
|
||||
#: netbox/ipam/tables/asn.py:76 netbox/netbox/navigation/menu.py:15
|
||||
#: netbox/ipam/tables/asn.py:66 netbox/netbox/navigation/menu.py:15
|
||||
#: netbox/netbox/navigation/menu.py:19
|
||||
msgid "Sites"
|
||||
msgstr ""
|
||||
@@ -7987,31 +7987,31 @@ msgstr ""
|
||||
msgid "Virtual Machines"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:3238
|
||||
#: netbox/dcim/views.py:3237
|
||||
#, python-brace-format
|
||||
msgid "Installed device {device} in bay {device_bay}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:3279
|
||||
#: netbox/dcim/views.py:3278
|
||||
#, python-brace-format
|
||||
msgid "Removed device {device} from bay {device_bay}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:3392 netbox/ipam/tables/ip.py:181
|
||||
#: netbox/dcim/views.py:3391 netbox/ipam/tables/ip.py:181
|
||||
msgid "Children"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:3865
|
||||
#: netbox/dcim/views.py:3864
|
||||
#, python-brace-format
|
||||
msgid "Added member <a href=\"{url}\">{device}</a>"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:3910
|
||||
#: netbox/dcim/views.py:3909
|
||||
#, python-brace-format
|
||||
msgid "Unable to remove master device {device} from the virtual chassis."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:3921
|
||||
#: netbox/dcim/views.py:3920
|
||||
#, python-brace-format
|
||||
msgid "Removed {device} from virtual chassis {chassis}"
|
||||
msgstr ""
|
||||
@@ -9267,113 +9267,113 @@ msgstr ""
|
||||
msgid "Filter must be defined as a dictionary mapping attributes to values."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:496
|
||||
#: netbox/extras/models/customfields.py:488
|
||||
msgid "True"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:497
|
||||
#: netbox/extras/models/customfields.py:489
|
||||
msgid "False"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:550
|
||||
#: netbox/extras/models/customfields.py:598
|
||||
#: netbox/extras/models/customfields.py:542
|
||||
#: netbox/extras/models/customfields.py:590
|
||||
#, python-brace-format
|
||||
msgid "Values must match this regex: <code>{regex}</code>"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:700
|
||||
#: netbox/extras/models/customfields.py:707
|
||||
#: netbox/extras/models/customfields.py:692
|
||||
#: netbox/extras/models/customfields.py:699
|
||||
msgid "Value must be a string."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:702
|
||||
#: netbox/extras/models/customfields.py:709
|
||||
#: netbox/extras/models/customfields.py:694
|
||||
#: netbox/extras/models/customfields.py:701
|
||||
#, python-brace-format
|
||||
msgid "Value must match regex '{regex}'"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:714
|
||||
#: netbox/extras/models/customfields.py:706
|
||||
msgid "Value must be an integer."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:717
|
||||
#: netbox/extras/models/customfields.py:732
|
||||
#: netbox/extras/models/customfields.py:709
|
||||
#: netbox/extras/models/customfields.py:724
|
||||
#, python-brace-format
|
||||
msgid "Value must be at least {minimum}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:721
|
||||
#: netbox/extras/models/customfields.py:736
|
||||
#: netbox/extras/models/customfields.py:713
|
||||
#: netbox/extras/models/customfields.py:728
|
||||
#, python-brace-format
|
||||
msgid "Value must not exceed {maximum}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:729
|
||||
#: netbox/extras/models/customfields.py:721
|
||||
msgid "Value must be a decimal."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:741
|
||||
#: netbox/extras/models/customfields.py:733
|
||||
msgid "Value must be true or false."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:749
|
||||
#: netbox/extras/models/customfields.py:741
|
||||
msgid "Date values must be in ISO 8601 format (YYYY-MM-DD)."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:758
|
||||
#: netbox/extras/models/customfields.py:750
|
||||
msgid "Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS)."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:765
|
||||
#: netbox/extras/models/customfields.py:757
|
||||
#, python-brace-format
|
||||
msgid "Invalid choice ({value}) for choice set {choiceset}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:775
|
||||
#: netbox/extras/models/customfields.py:767
|
||||
#, python-brace-format
|
||||
msgid "Invalid choice(s) ({value}) for choice set {choiceset}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:784
|
||||
#: netbox/extras/models/customfields.py:776
|
||||
#, python-brace-format
|
||||
msgid "Value must be an object ID, not {type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:790
|
||||
#: netbox/extras/models/customfields.py:782
|
||||
#, python-brace-format
|
||||
msgid "Value must be a list of object IDs, not {type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:794
|
||||
#: netbox/extras/models/customfields.py:786
|
||||
#, python-brace-format
|
||||
msgid "Found invalid object ID: {id}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:797
|
||||
#: netbox/extras/models/customfields.py:789
|
||||
msgid "Required field cannot be empty."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:817
|
||||
#: netbox/extras/models/customfields.py:809
|
||||
msgid "Base set of predefined choices (optional)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:829
|
||||
#: netbox/extras/models/customfields.py:821
|
||||
msgid "Choices are automatically ordered alphabetically"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:836
|
||||
#: netbox/extras/models/customfields.py:828
|
||||
msgid "custom field choice set"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:837
|
||||
#: netbox/extras/models/customfields.py:829
|
||||
msgid "custom field choice sets"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:879
|
||||
#: netbox/extras/models/customfields.py:871
|
||||
msgid "Must define base or extra choices."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:903
|
||||
#: netbox/extras/models/customfields.py:895
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Cannot remove choice {choice} as there are {model} objects which reference "
|
||||
@@ -10130,7 +10130,7 @@ msgstr ""
|
||||
msgid "Customer"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/fields.py:40
|
||||
#: netbox/ipam/fields.py:39
|
||||
#, python-brace-format
|
||||
msgid "Invalid IP address format: {address}"
|
||||
msgstr ""
|
||||
@@ -10356,9 +10356,9 @@ msgstr ""
|
||||
#: netbox/ipam/forms/filtersets.py:151 netbox/ipam/forms/model_forms.py:100
|
||||
#: netbox/ipam/forms/model_forms.py:113 netbox/ipam/forms/model_forms.py:136
|
||||
#: netbox/ipam/forms/model_forms.py:155 netbox/ipam/models/asns.py:32
|
||||
#: netbox/ipam/models/asns.py:132 netbox/ipam/models/ip.py:72
|
||||
#: netbox/ipam/models/asns.py:101 netbox/ipam/models/ip.py:72
|
||||
#: netbox/ipam/models/ip.py:88 netbox/ipam/tables/asn.py:20
|
||||
#: netbox/ipam/tables/asn.py:55 netbox/templates/ipam/aggregate.html:18
|
||||
#: netbox/ipam/tables/asn.py:45 netbox/templates/ipam/aggregate.html:18
|
||||
#: netbox/templates/ipam/asn.html:27 netbox/templates/ipam/asnrange.html:19
|
||||
#: netbox/templates/ipam/rir.html:19
|
||||
msgid "RIR"
|
||||
@@ -10419,8 +10419,8 @@ msgid "DNS name"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_edit.py:376 netbox/ipam/forms/bulk_edit.py:573
|
||||
#: netbox/ipam/forms/bulk_import.py:443 netbox/ipam/forms/bulk_import.py:561
|
||||
#: netbox/ipam/forms/bulk_import.py:589 netbox/ipam/forms/filtersets.py:414
|
||||
#: netbox/ipam/forms/bulk_import.py:447 netbox/ipam/forms/bulk_import.py:565
|
||||
#: netbox/ipam/forms/bulk_import.py:593 netbox/ipam/forms/filtersets.py:414
|
||||
#: netbox/ipam/forms/filtersets.py:604 netbox/templates/ipam/fhrpgroup.html:22
|
||||
#: netbox/templates/ipam/inc/panels/fhrp_groups.html:24
|
||||
#: netbox/templates/ipam/service.html:34
|
||||
@@ -10464,7 +10464,7 @@ msgstr ""
|
||||
msgid "VLAN ID ranges"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_edit.py:516 netbox/ipam/forms/bulk_import.py:518
|
||||
#: netbox/ipam/forms/bulk_edit.py:516 netbox/ipam/forms/bulk_import.py:522
|
||||
#: netbox/ipam/forms/filtersets.py:579 netbox/ipam/models/vlans.py:250
|
||||
#: netbox/ipam/tables/vlans.py:106
|
||||
msgid "Q-in-Q role"
|
||||
@@ -10478,7 +10478,7 @@ msgstr ""
|
||||
msgid "Site & Group"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_edit.py:557 netbox/ipam/forms/bulk_import.py:548
|
||||
#: netbox/ipam/forms/bulk_edit.py:557 netbox/ipam/forms/bulk_import.py:552
|
||||
#: netbox/ipam/forms/model_forms.py:726 netbox/ipam/tables/vlans.py:259
|
||||
#: netbox/templates/ipam/vlantranslationrule.html:14
|
||||
#: netbox/vpn/forms/model_forms.py:322 netbox/vpn/forms/model_forms.py:359
|
||||
@@ -10523,86 +10523,86 @@ msgstr ""
|
||||
msgid "Scope ID"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:327 netbox/ipam/forms/filtersets.py:636
|
||||
#: netbox/ipam/forms/bulk_import.py:331 netbox/ipam/forms/filtersets.py:636
|
||||
#: netbox/ipam/forms/model_forms.py:306 netbox/ipam/forms/model_forms.py:336
|
||||
#: netbox/ipam/forms/model_forms.py:517 netbox/templates/ipam/fhrpgroup.html:19
|
||||
msgid "FHRP Group"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:331
|
||||
#: netbox/ipam/forms/bulk_import.py:335
|
||||
msgid "Assigned FHRP Group name"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:335
|
||||
#: netbox/ipam/forms/bulk_import.py:339
|
||||
msgid "Make this the primary IP for the assigned device"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:339
|
||||
#: netbox/ipam/forms/bulk_import.py:343
|
||||
msgid "Is out-of-band"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:340
|
||||
#: netbox/ipam/forms/bulk_import.py:344
|
||||
msgid "Designate this as the out-of-band IP address for the assigned device"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:394
|
||||
#: netbox/ipam/forms/bulk_import.py:398
|
||||
msgid "No device or virtual machine specified; cannot set as primary IP"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:398
|
||||
#: netbox/ipam/forms/bulk_import.py:402
|
||||
msgid "No device specified; cannot set as out-of-band IP"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:402
|
||||
#: netbox/ipam/forms/bulk_import.py:406
|
||||
msgid "Cannot set out-of-band IP for virtual machines"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:406
|
||||
#: netbox/ipam/forms/bulk_import.py:410
|
||||
msgid "No interface specified; cannot set as primary IP"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:410
|
||||
#: netbox/ipam/forms/bulk_import.py:414
|
||||
msgid "No interface specified; cannot set as out-of-band IP"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:447
|
||||
#: netbox/ipam/forms/bulk_import.py:451
|
||||
msgid "Auth type"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:496
|
||||
#: netbox/ipam/forms/bulk_import.py:500
|
||||
msgid "Assigned VLAN group"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:528
|
||||
#: netbox/ipam/forms/bulk_import.py:532
|
||||
msgid "Service VLAN (for Q-in-Q/802.1ad customer VLANs)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:551 netbox/ipam/models/vlans.py:369
|
||||
#: netbox/ipam/forms/bulk_import.py:555 netbox/ipam/models/vlans.py:369
|
||||
msgid "VLAN translation policy"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:563 netbox/ipam/forms/bulk_import.py:591
|
||||
#: netbox/ipam/forms/bulk_import.py:567 netbox/ipam/forms/bulk_import.py:595
|
||||
msgid "IP protocol"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:575
|
||||
#: netbox/ipam/forms/bulk_import.py:579
|
||||
msgid "Parent type (app & model)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:582
|
||||
#: netbox/ipam/forms/bulk_import.py:586
|
||||
msgid "Parent object name"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:586
|
||||
#: netbox/ipam/forms/bulk_import.py:590
|
||||
msgid "Parent object ID"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:638
|
||||
#: netbox/ipam/forms/bulk_import.py:642
|
||||
msgid ""
|
||||
"One of parent or parent_object_id must be included with parent_object_type"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/forms/bulk_import.py:651
|
||||
#: netbox/ipam/forms/bulk_import.py:655
|
||||
#, python-brace-format
|
||||
msgid "{ip} is not assigned to this parent."
|
||||
msgstr ""
|
||||
@@ -10834,16 +10834,16 @@ msgstr ""
|
||||
msgid "ASN ranges"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/models/asns.py:63
|
||||
#: netbox/ipam/models/asns.py:70
|
||||
#, python-brace-format
|
||||
msgid "Starting ASN ({start}) must be lower than ending ASN ({end})."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/models/asns.py:133
|
||||
#: netbox/ipam/models/asns.py:102
|
||||
msgid "Regional Internet Registry responsible for this AS number space"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/models/asns.py:138
|
||||
#: netbox/ipam/models/asns.py:107
|
||||
msgid "16- or 32-bit autonomous system number"
|
||||
msgstr ""
|
||||
|
||||
@@ -11261,23 +11261,15 @@ msgstr ""
|
||||
msgid "route targets"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/tables/asn.py:26
|
||||
msgid "Start (ASDOT)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/tables/asn.py:31
|
||||
msgid "End (ASDOT)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/tables/asn.py:62
|
||||
#: netbox/ipam/tables/asn.py:52
|
||||
msgid "ASDOT"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/tables/asn.py:67
|
||||
#: netbox/ipam/tables/asn.py:57
|
||||
msgid "Site Count"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/tables/asn.py:72
|
||||
#: netbox/ipam/tables/asn.py:62
|
||||
msgid "Provider Count"
|
||||
msgstr ""
|
||||
|
||||
@@ -12531,34 +12523,30 @@ msgid "Japanese"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/settings.py:830
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/settings.py:831
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/settings.py:832
|
||||
#: netbox/netbox/settings.py:831
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/settings.py:833
|
||||
#: netbox/netbox/settings.py:832
|
||||
msgid "Portuguese"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/settings.py:834
|
||||
#: netbox/netbox/settings.py:833
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/settings.py:835
|
||||
#: netbox/netbox/settings.py:834
|
||||
msgid "Turkish"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/settings.py:836
|
||||
#: netbox/netbox/settings.py:835
|
||||
msgid "Ukrainian"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/settings.py:837
|
||||
#: netbox/netbox/settings.py:836
|
||||
msgid "Chinese"
|
||||
msgstr ""
|
||||
|
||||
@@ -12593,75 +12581,69 @@ msgstr ""
|
||||
msgid "Dummy Plugin"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:124
|
||||
#: netbox/netbox/views/generic/bulk_views.py:122
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"There was an error rendering the selected export template ({template}): "
|
||||
"{error}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:390
|
||||
#: netbox/netbox/views/generic/bulk_views.py:388
|
||||
msgid "Must be a list."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:400
|
||||
#: netbox/netbox/views/generic/bulk_views.py:398
|
||||
msgid "Must be a dictionary."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:453
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Duplicate objects found: {model} with ID(s) {ids} appears multiple times"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:475
|
||||
#: netbox/netbox/views/generic/bulk_views.py:461
|
||||
#, python-brace-format
|
||||
msgid "Object with ID {id} does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:560
|
||||
#: netbox/netbox/views/generic/bulk_views.py:546
|
||||
#, python-brace-format
|
||||
msgid "Bulk import {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:576
|
||||
#: netbox/netbox/views/generic/bulk_views.py:562
|
||||
#, python-brace-format
|
||||
msgid "Imported {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:766
|
||||
#: netbox/netbox/views/generic/bulk_views.py:752
|
||||
#, python-brace-format
|
||||
msgid "Bulk edit {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:782
|
||||
#: netbox/netbox/views/generic/bulk_views.py:768
|
||||
#, python-brace-format
|
||||
msgid "Updated {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:815
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1050
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1098
|
||||
#: netbox/netbox/views/generic/bulk_views.py:801
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1036
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1084
|
||||
#, python-brace-format
|
||||
msgid "No {object_type} were selected."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:908
|
||||
#: netbox/netbox/views/generic/bulk_views.py:894
|
||||
#, python-brace-format
|
||||
msgid "Renamed {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:978
|
||||
#: netbox/netbox/views/generic/bulk_views.py:964
|
||||
#, python-brace-format
|
||||
msgid "Bulk delete {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1005
|
||||
#: netbox/netbox/views/generic/bulk_views.py:991
|
||||
#, python-brace-format
|
||||
msgid "Deleted {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1022
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1008
|
||||
msgid "Deletion failed due to the presence of one or more dependent objects."
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -50,21 +50,21 @@ class RestrictedQuerySet(QuerySet):
|
||||
|
||||
# Bypass restriction for superusers and exempt views
|
||||
if user and user.is_superuser or permission_is_exempt(permission_required):
|
||||
return self
|
||||
qs = self
|
||||
|
||||
# User is anonymous or has not been granted the requisite permission
|
||||
if user is None or not user.is_authenticated or permission_required not in user.get_all_permissions():
|
||||
return self.none()
|
||||
elif user is None or not user.is_authenticated or permission_required not in user.get_all_permissions():
|
||||
qs = self.none()
|
||||
|
||||
# Filter the queryset to include only objects with allowed attributes
|
||||
constraints = user._object_perm_cache[permission_required]
|
||||
tokens = {
|
||||
CONSTRAINT_TOKEN_USER: user,
|
||||
}
|
||||
if attrs := qs_filter_from_constraints(constraints, tokens):
|
||||
else:
|
||||
tokens = {
|
||||
CONSTRAINT_TOKEN_USER: user,
|
||||
}
|
||||
attrs = qs_filter_from_constraints(user._object_perm_cache[permission_required], tokens)
|
||||
# #8715: Avoid duplicates when JOIN on many-to-many fields without using DISTINCT.
|
||||
# DISTINCT acts globally on the entire request, which may not be desirable.
|
||||
allowed_objects = self.model.objects.filter(attrs)
|
||||
return self.filter(pk__in=allowed_objects)
|
||||
qs = self.filter(pk__in=allowed_objects)
|
||||
|
||||
return self
|
||||
return qs
|
||||
|
||||
@@ -141,8 +141,8 @@ class ModelTestCase(TestCase):
|
||||
elif value and type(field) is GenericForeignKey:
|
||||
model_dict[key] = value.pk
|
||||
|
||||
# Handle API output
|
||||
elif api:
|
||||
|
||||
# Replace ContentType numeric IDs with <app_label>.<model>
|
||||
if type(getattr(instance, key)) in (ContentType, ObjectType):
|
||||
object_type = ObjectType.objects.get(pk=value)
|
||||
@@ -152,13 +152,9 @@ class ModelTestCase(TestCase):
|
||||
elif type(value) is IPNetwork:
|
||||
model_dict[key] = str(value)
|
||||
|
||||
# Normalize arrays of numeric ranges (e.g. VLAN IDs or port ranges).
|
||||
# DB uses canonical half-open [lo, hi) via NumericRange; API uses inclusive [lo, hi].
|
||||
# Convert to inclusive pairs for stable API comparisons.
|
||||
elif type(field) is ArrayField and issubclass(type(field.base_field), RangeField):
|
||||
model_dict[key] = [[r.lower, r.upper - 1] for r in value]
|
||||
|
||||
else:
|
||||
field = instance._meta.get_field(key)
|
||||
|
||||
# Convert ArrayFields to CSV strings
|
||||
if type(field) is ArrayField:
|
||||
if getattr(field.base_field, 'choices', None):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[project]
|
||||
name = "netbox"
|
||||
version = "4.4.9"
|
||||
version = "4.4.7"
|
||||
requires-python = ">=3.10"
|
||||
description = "The premier source of truth powering network automation."
|
||||
readme = "README.md"
|
||||
|
||||
@@ -23,7 +23,7 @@ gunicorn==23.0.0
|
||||
Jinja2==3.1.6
|
||||
jsonschema==4.25.1
|
||||
Markdown==3.10
|
||||
mkdocs-material==9.7.1
|
||||
mkdocs-material==9.7.0
|
||||
mkdocstrings==1.0.0
|
||||
mkdocstrings-python==2.0.1
|
||||
netaddr==1.3.0
|
||||
@@ -33,11 +33,11 @@ psycopg[c,pool]==3.3.2
|
||||
PyYAML==6.0.3
|
||||
requests==2.32.5
|
||||
rq==2.6.1
|
||||
social-auth-app-django==5.7.0
|
||||
social-auth-core==4.8.3
|
||||
social-auth-app-django==5.6.0
|
||||
social-auth-core==4.8.1
|
||||
sorl-thumbnail==12.11.0
|
||||
strawberry-graphql==0.287.3
|
||||
strawberry-graphql==0.287.2
|
||||
strawberry-graphql-django==0.70.1
|
||||
svgwrite==1.4.3
|
||||
tablib==3.9.0
|
||||
tzdata==2025.3
|
||||
tzdata==2025.2
|
||||
|
||||
Reference in New Issue
Block a user