Compare commits

...

19 Commits

Author SHA1 Message Date
Jeremy Stretch
f845b2cf07 Release v4.2.2 2025-01-17 15:05:09 -05:00
atownson
2ed4a2b005 Fixes: #18369 - Remove the json filter for protection rules (#18388)
* Remove the json filter for protection rules

* Configure PROTECTION_RULE config attribute to use ConfigJSONEncoder as serializer

* Tweak getattr()

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2025-01-17 14:02:12 -05:00
Jeremy Stretch
5b9210dfa5 Fixes #18392: Exclude config contexts assigned to locations for VMs 2025-01-17 13:14:05 -05:00
Jeremy Stretch
4a13664e0f Closes #18425: Remove the triage priority field from GitHub issue templates 2025-01-17 11:06:17 -05:00
Jeremy Stretch
a9f3c74b0c Fixes #18379: Ensure RSS feed content within dashboard widget is sanitized 2025-01-17 10:25:22 -05:00
Brian Tiemann
50b7f46fc0 Migrate DEFAULT_FILE_STORAGE to STORAGES 2025-01-17 09:04:51 -05:00
Brian Tiemann
07ad4c1321 Make GFK scope field sortable=False on tables where it appears 2025-01-17 08:52:12 -05:00
bctiemann
4a1fea3504 Fixes: #18336 - Perform Rack object validation of u_height and starting_unit on rack_type if present (#18395)
* Perform Rack object validation of u_height and starting_unit on rack_type if present

* Calculate effective values before doing validation
2025-01-17 08:45:17 -05:00
bctiemann
993d8f1480 Fixes: #18373 - Fix validation of site in Assign Device to Cluster flow (#18375)
* Fix validation of site in Assign Device to Cluster flow

* Validate Location as well as Site scope
2025-01-17 08:35:17 -05:00
bctiemann
c3efa2149c Fixes: #18350 - Remove 'site' and 'provider_network' from CircuitTerminationIndex.display_attrs (#18351)
* Remove 'site' and 'provider_network' from CircuitTerminationIndex.display_attrs

* Use '_site' and '_provider_network' in display_attrs

* Replace private fields with 'termination'
2025-01-17 08:28:43 -05:00
Jeremy Stretch
a75fa53d4d Closes #18348: Disable legacy pre-commit hook script 2025-01-13 08:50:34 -05:00
Jeremy Stretch
e75d327f38 Fixes #18376: Include tagged VLANs in interfaces list for Q-in-Q interfaces 2025-01-10 09:10:34 -05:00
github-actions
a79d869bd8 Update source translation strings 2025-01-10 05:02:08 +00:00
Brian Tiemann
32422d1683 Don't cache CACHE_KEY_CATALOG_ERROR if ISOLATED_DEPLOYMENT is True 2025-01-09 15:21:27 -05:00
Jeremy Stretch
571f604ce8 Fixes #18368: Restore missing fields on REST API serializer for MAC addresses 2025-01-09 14:53:03 -05:00
Jeremy Stretch
b12c8c880f Fixes #18363: Fix assignment of MAC addresses to interfaces via REST API (#18367)
* Fixes #18363: Fix assignment of MAC addresses to interfaces via REST API

* Add missing API & view tests
2025-01-09 13:55:19 -05:00
Jeremy Stretch
b11f179527 Closes #18362: Create a system job for census reporting 2025-01-09 11:56:09 -05:00
Brian Tiemann
80e1fd02bb Update docs to indicate PostgreSQL 13+ requirement 2025-01-09 10:58:51 -05:00
github-actions
4090afbf24 Update source translation strings 2025-01-09 05:02:09 +00:00
32 changed files with 301 additions and 199 deletions

View File

@@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v4.2.1 placeholder: v4.2.2
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
@@ -27,19 +27,6 @@ body:
- Other - Other
validations: validations:
required: true required: true
- type: dropdown
attributes:
label: Triage priority
description: >
Issue triage may be prioritized in some cases. Select whichever of the following
conditions applies, if any.
options:
- I volunteer to perform this work (if approved)
- I'm a NetBox Labs customer
- N/A
default: 2
validations:
required: true
- type: textarea - type: textarea
attributes: attributes:
label: Proposed functionality label: Proposed functionality

View File

@@ -22,24 +22,11 @@ body:
- Self-hosted - Self-hosted
validations: validations:
required: true required: true
- type: dropdown
attributes:
label: Triage priority
description: >
Issue triage may be prioritized in some cases. Select whichever of the following
conditions applies, if any.
options:
- I volunteer to perform this work (if approved)
- I'm a NetBox Labs customer
- N/A
default: 2
validations:
required: true
- type: input - type: input
attributes: attributes:
label: NetBox Version label: NetBox Version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v4.2.1 placeholder: v4.2.2
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@@ -8,8 +8,6 @@ django-cors-headers
# Runtime UI tool for debugging Django # Runtime UI tool for debugging Django
# https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst # https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst
# Pinned for DNS looukp bug; see https://github.com/netbox-community/netbox/issues/16454
# and https://github.com/jazzband/django-debug-toolbar/issues/1927
django-debug-toolbar django-debug-toolbar
# Library for writing reusable URL query filters # Library for writing reusable URL query filters

View File

@@ -21,7 +21,7 @@ The following sections detail how to set up a new instance of NetBox:
| Dependency | Supported Versions | | Dependency | Supported Versions |
|------------|--------------------| |------------|--------------------|
| Python | 3.10, 3.11, 3.12 | | Python | 3.10, 3.11, 3.12 |
| PostgreSQL | 12+ | | PostgreSQL | 13+ |
| Redis | 4.0+ | | Redis | 4.0+ |
Below is a simplified overview of the NetBox application stack for reference: Below is a simplified overview of the NetBox application stack for reference:

View File

@@ -20,7 +20,7 @@ NetBox requires the following dependencies:
| Dependency | Supported Versions | | Dependency | Supported Versions |
|------------|--------------------| |------------|--------------------|
| Python | 3.10, 3.11, 3.12 | | Python | 3.10, 3.11, 3.12 |
| PostgreSQL | 12+ | | PostgreSQL | 13+ |
| Redis | 4.0+ | | Redis | 4.0+ |
## 3. Install the Latest Release ## 3. Install the Latest Release

View File

@@ -1,5 +1,25 @@
# NetBox v4.2 # NetBox v4.2
## v4.2.2 (2025-01-17)
### Bug Fixes
* [#18336](https://github.com/netbox-community/netbox/issues/18336) - Validate new rack height against installed devices when changing a rack's type
* [#18350](https://github.com/netbox-community/netbox/issues/18350) - Fix `FieldDoesNotExist` exception when global search results include a circuit termination
* [#18353](https://github.com/netbox-community/netbox/issues/18353) - Disable fetching of plugin catalog data when `ISOLATED_DEPLOYMENT` is enabled
* [#18362](https://github.com/netbox-community/netbox/issues/18362) - Avoid transmitting census data on every worker restart
* [#18363](https://github.com/netbox-community/netbox/issues/18363) - Fix support for assigning a MAC address to an interface via the REST API
* [#18368](https://github.com/netbox-community/netbox/issues/18368) - Restore missing attributes from REST API serializer for MAC addresses (`tags`, `created`, `last_updated`, and custom fields)
* [#18369](https://github.com/netbox-community/netbox/issues/18369) - Fix `TypeError` exception when rendering the system configuration view with one or more custom classes defined under `PROTECTION_RULES`
* [#18373](https://github.com/netbox-community/netbox/issues/18373) - Fix `AttributeError` exception when attempting to assign host devices to a cluster
* [#18376](https://github.com/netbox-community/netbox/issues/18376) - Fix the display of tagged VLANs in interfaces list for Q-in-Q interfaces
* [#18379](https://github.com/netbox-community/netbox/issues/18379) - Ensure RSS feed dashboard widget content is sanitized
* [#18392](https://github.com/netbox-community/netbox/issues/18392) - Virtual machines should not inherit config contexts assigned to locations
* [#18400](https://github.com/netbox-community/netbox/issues/18400) - Fix support for `STORAGE_BACKEND` configuration parameter
* [#18406](https://github.com/netbox-community/netbox/issues/18406) - Scope column headers in object lists should not be orderable
---
## v4.2.1 (2025-01-08) ## v4.2.1 (2025-01-08)
### Bug Fixes ### Bug Fixes

View File

@@ -34,7 +34,7 @@ class CircuitTerminationIndex(SearchIndex):
('port_speed', 2000), ('port_speed', 2000),
('upstream_speed', 2000), ('upstream_speed', 2000),
) )
display_attrs = ('circuit', 'site', 'provider_network', 'description') display_attrs = ('circuit', 'termination', 'description')
@register_search @register_search

View File

@@ -1,8 +1,11 @@
import logging import logging
import requests
import sys
from netbox.jobs import JobRunner from django.conf import settings
from netbox.jobs import JobRunner, system_job
from netbox.search.backends import search_backend from netbox.search.backends import search_backend
from .choices import DataSourceStatusChoices from .choices import DataSourceStatusChoices, JobIntervalChoices
from .exceptions import SyncError from .exceptions import SyncError
from .models import DataSource from .models import DataSource
@@ -31,3 +34,44 @@ class SyncDataSourceJob(JobRunner):
if type(e) is SyncError: if type(e) is SyncError:
logging.error(e) logging.error(e)
raise e raise e
@system_job(interval=JobIntervalChoices.INTERVAL_DAILY)
class SystemHousekeepingJob(JobRunner):
"""
Perform daily system housekeeping functions.
"""
class Meta:
name = "System Housekeeping"
def run(self, *args, **kwargs):
# Skip if running in development or test mode
if settings.DEBUG or 'test' in sys.argv:
return
# TODO: Migrate other housekeeping functions from the `housekeeping` management command.
self.send_census_report()
@staticmethod
def send_census_report():
"""
Send a census report (if enabled).
"""
# Skip if census reporting is disabled
if settings.ISOLATED_DEPLOYMENT or not settings.CENSUS_REPORTING_ENABLED:
return
census_data = {
'version': settings.RELEASE.full_version,
'python_version': sys.version.split()[0],
'deployment_id': settings.DEPLOYMENT_ID,
}
try:
requests.get(
url=settings.CENSUS_URL,
params=census_data,
timeout=3,
proxies=settings.HTTP_PROXIES
)
except requests.exceptions.RequestException:
pass

View File

@@ -570,8 +570,9 @@ class SystemView(UserPassesTestMixin, View):
return response return response
# Serialize any CustomValidator classes # Serialize any CustomValidator classes
if hasattr(config, 'CUSTOM_VALIDATORS') and config.CUSTOM_VALIDATORS: for attr in ['CUSTOM_VALIDATORS', 'PROTECTION_RULES']:
config.CUSTOM_VALIDATORS = json.dumps(config.CUSTOM_VALIDATORS, cls=ConfigJSONEncoder, indent=4) if hasattr(config, attr) and getattr(config, attr, None):
setattr(config, attr, json.dumps(getattr(config, attr), cls=ConfigJSONEncoder, indent=4))
return render(request, 'core/system.html', { return render(request, 'core/system.html', {
'stats': stats, 'stats': stats,
@@ -594,7 +595,7 @@ class BasePluginView(UserPassesTestMixin, View):
catalog_plugins_error = cache.get(self.CACHE_KEY_CATALOG_ERROR, default=False) catalog_plugins_error = cache.get(self.CACHE_KEY_CATALOG_ERROR, default=False)
if not catalog_plugins_error: if not catalog_plugins_error:
catalog_plugins = get_catalog_plugins() catalog_plugins = get_catalog_plugins()
if not catalog_plugins: if not catalog_plugins and not settings.ISOLATED_DEPLOYMENT:
# Cache for 5 minutes to avoid spamming connection # Cache for 5 minutes to avoid spamming connection
cache.set(self.CACHE_KEY_CATALOG_ERROR, True, 300) cache.set(self.CACHE_KEY_CATALOG_ERROR, True, 300)
messages.warning(request, _("Plugins catalog could not be loaded")) messages.warning(request, _("Plugins catalog could not be loaded"))

View File

@@ -170,8 +170,8 @@ class MACAddressSerializer(NetBoxModelSerializer):
class Meta: class Meta:
model = MACAddress model = MACAddress
fields = [ fields = [
'id', 'url', 'display_url', 'display', 'mac_address', 'assigned_object_type', 'assigned_object', 'id', 'url', 'display_url', 'display', 'mac_address', 'assigned_object_type', 'assigned_object_id',
'description', 'comments', 'assigned_object', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
] ]
brief_fields = ('id', 'url', 'display', 'mac_address', 'description') brief_fields = ('id', 'url', 'display', 'mac_address', 'description')

View File

@@ -374,21 +374,26 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
if not self._state.adding: if not self._state.adding:
mounted_devices = Device.objects.filter(rack=self).exclude(position__isnull=True).order_by('position') mounted_devices = Device.objects.filter(rack=self).exclude(position__isnull=True).order_by('position')
effective_u_height = self.rack_type.u_height if self.rack_type else self.u_height
effective_starting_unit = self.rack_type.starting_unit if self.rack_type else self.starting_unit
# Validate that Rack is tall enough to house the highest mounted Device # Validate that Rack is tall enough to house the highest mounted Device
if top_device := mounted_devices.last(): if top_device := mounted_devices.last():
min_height = top_device.position + top_device.device_type.u_height - self.starting_unit min_height = top_device.position + top_device.device_type.u_height - effective_starting_unit
if self.u_height < min_height: if effective_u_height < min_height:
field = 'rack_type' if self.rack_type else 'u_height'
raise ValidationError({ raise ValidationError({
'u_height': _( field: _(
"Rack must be at least {min_height}U tall to house currently installed devices." "Rack must be at least {min_height}U tall to house currently installed devices."
).format(min_height=min_height) ).format(min_height=min_height)
}) })
# Validate that the Rack's starting unit is less than or equal to the position of the lowest mounted Device # Validate that the Rack's starting unit is less than or equal to the position of the lowest mounted Device
if last_device := mounted_devices.first(): if last_device := mounted_devices.first():
if self.starting_unit > last_device.position: if effective_starting_unit > last_device.position:
field = 'rack_type' if self.rack_type else 'starting_unit'
raise ValidationError({ raise ValidationError({
'starting_unit': _("Rack unit numbering must begin at {position} or less to house " field: _("Rack unit numbering must begin at {position} or less to house "
"currently installed devices.").format(position=last_device.position) "currently installed devices.").format(position=last_device.position)
}) })

View File

@@ -69,7 +69,11 @@ INTERFACE_FHRPGROUPS = """
""" """
INTERFACE_TAGGED_VLANS = """ INTERFACE_TAGGED_VLANS = """
{% if record.mode == 'tagged' %} {% load i18n %}
{% if record.mode == 'access' %}
{% elif record.mode == 'tagged-all' %}
{% trans "All" %}
{% else %}
{% if value.count > 3 %} {% if value.count > 3 %}
<a href="{% url 'ipam:vlan_list' %}?{{ record|meta:"model_name" }}_id={{ record.pk }}">{{ value.count }} VLANs</a> <a href="{% url 'ipam:vlan_list' %}?{{ record|meta:"model_name" }}_id={{ record.pk }}">{{ value.count }} VLANs</a>
{% else %} {% else %}
@@ -77,8 +81,6 @@ INTERFACE_TAGGED_VLANS = """
<a href="{{ vlan.get_absolute_url }}">{{ vlan }}</a><br /> <a href="{{ vlan.get_absolute_url }}">{{ vlan }}</a><br />
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% elif record.mode == 'tagged-all' %}
All
{% endif %} {% endif %}
""" """

View File

@@ -2447,3 +2447,46 @@ class VirtualDeviceContextTest(APIViewTestCases.APIViewTestCase):
# Omit identifier to test uniqueness constraint # Omit identifier to test uniqueness constraint
}, },
] ]
class MACAddressTest(APIViewTestCases.APIViewTestCase):
model = MACAddress
brief_fields = ['description', 'display', 'id', 'mac_address', 'url']
bulk_update_data = {
'description': 'New description',
}
@classmethod
def setUpTestData(cls):
device = create_test_device(name='Device 1')
interfaces = (
Interface(device=device, name='Interface 1', type='1000base-t'),
Interface(device=device, name='Interface 2', type='1000base-t'),
Interface(device=device, name='Interface 3', type='1000base-t'),
Interface(device=device, name='Interface 4', type='1000base-t'),
Interface(device=device, name='Interface 5', type='1000base-t'),
)
Interface.objects.bulk_create(interfaces)
mac_addresses = (
MACAddress(mac_address='00:00:00:00:00:01', assigned_object=interfaces[0]),
MACAddress(mac_address='00:00:00:00:00:02', assigned_object=interfaces[1]),
MACAddress(mac_address='00:00:00:00:00:03', assigned_object=interfaces[2]),
)
MACAddress.objects.bulk_create(mac_addresses)
cls.create_data = [
{
'mac_address': '00:00:00:00:00:04',
'assigned_object_type': 'dcim.interface',
'assigned_object_id': interfaces[3].pk,
},
{
'mac_address': '00:00:00:00:00:05',
'assigned_object_type': 'dcim.interface',
'assigned_object_id': interfaces[4].pk,
},
{
'mac_address': '00:00:00:00:00:06',
},
]

View File

@@ -3470,3 +3470,54 @@ class VirtualDeviceContextTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.bulk_edit_data = { cls.bulk_edit_data = {
'status': VirtualDeviceContextStatusChoices.STATUS_OFFLINE, 'status': VirtualDeviceContextStatusChoices.STATUS_OFFLINE,
} }
class MACAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = MACAddress
@classmethod
def setUpTestData(cls):
device = create_test_device(name='Device 1')
interfaces = (
Interface(device=device, name='Interface 1', type='1000base-t'),
Interface(device=device, name='Interface 2', type='1000base-t'),
Interface(device=device, name='Interface 3', type='1000base-t'),
Interface(device=device, name='Interface 4', type='1000base-t'),
Interface(device=device, name='Interface 5', type='1000base-t'),
Interface(device=device, name='Interface 6', type='1000base-t'),
)
Interface.objects.bulk_create(interfaces)
mac_addresses = (
MACAddress(mac_address='00:00:00:00:00:01', assigned_object=interfaces[0]),
MACAddress(mac_address='00:00:00:00:00:02', assigned_object=interfaces[1]),
MACAddress(mac_address='00:00:00:00:00:03', assigned_object=interfaces[2]),
)
MACAddress.objects.bulk_create(mac_addresses)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'mac_address': EUI('00:00:00:00:00:04'),
'description': 'New MAC address',
'interface_id': interfaces[3].pk,
'tags': [t.pk for t in tags],
}
cls.csv_data = (
"mac_address,device,interface",
"00:00:00:00:00:04,Device 1,Interface 4",
"00:00:00:00:00:05,Device 1,Interface 5",
"00:00:00:00:00:06,Device 1,Interface 6",
)
cls.csv_update_data = (
"id,mac_address",
f"{mac_addresses[0].pk},00:00:00:00:00:0a",
f"{mac_addresses[1].pk},00:00:00:00:00:0b",
f"{mac_addresses[2].pk},00:00:00:00:00:0c",
)
cls.bulk_edit_data = {
'description': 'New description',
}

View File

@@ -120,11 +120,12 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
is_active=True, is_active=True,
) )
# Apply Location & DeviceType filters only for VirtualMachines
if self.model._meta.model_name == 'device': if self.model._meta.model_name == 'device':
base_query.add((Q(locations=OuterRef('location')) | Q(locations=None)), Q.AND) base_query.add((Q(locations=OuterRef('location')) | Q(locations=None)), Q.AND)
base_query.add((Q(device_types=OuterRef('device_type')) | Q(device_types=None)), Q.AND) base_query.add((Q(device_types=OuterRef('device_type')) | Q(device_types=None)), Q.AND)
elif self.model._meta.model_name == 'virtualmachine': elif self.model._meta.model_name == 'virtualmachine':
base_query.add(Q(locations=None), Q.AND)
base_query.add(Q(device_types=None), Q.AND) base_query.add(Q(device_types=None), Q.AND)
base_query.add((Q(roles=OuterRef('role')) | Q(roles=None)), Q.AND) base_query.add((Q(roles=OuterRef('role')) | Q(roles=None)), Q.AND)

View File

@@ -192,7 +192,8 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
) )
scope = tables.Column( scope = tables.Column(
verbose_name=_('Scope'), verbose_name=_('Scope'),
linkify=True linkify=True,
orderable=False
) )
vlan_group = tables.Column( vlan_group = tables.Column(
accessor='vlan__group', accessor='vlan__group',

View File

@@ -5,9 +5,7 @@ import os
import platform import platform
import sys import sys
import warnings import warnings
from urllib.parse import urlencode
import requests
from django.contrib.messages import constants as messages from django.contrib.messages import constants as messages
from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.validators import URLValidator from django.core.validators import URLValidator
@@ -224,8 +222,18 @@ DATABASES = {
# Storage backend # Storage backend
# #
# Default STORAGES for Django
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}
if STORAGE_BACKEND is not None: if STORAGE_BACKEND is not None:
DEFAULT_FILE_STORAGE = STORAGE_BACKEND STORAGES['default']['BACKEND'] = STORAGE_BACKEND
# django-storages # django-storages
if STORAGE_BACKEND.startswith('storages.'): if STORAGE_BACKEND.startswith('storages.'):
@@ -583,17 +591,6 @@ if SENTRY_ENABLED:
# Calculate a unique deployment ID from the secret key # Calculate a unique deployment ID from the secret key
DEPLOYMENT_ID = hashlib.sha256(SECRET_KEY.encode('utf-8')).hexdigest()[:16] DEPLOYMENT_ID = hashlib.sha256(SECRET_KEY.encode('utf-8')).hexdigest()[:16]
CENSUS_URL = 'https://census.netbox.oss.netboxlabs.com/api/v1/' CENSUS_URL = 'https://census.netbox.oss.netboxlabs.com/api/v1/'
CENSUS_PARAMS = {
'version': RELEASE.full_version,
'python_version': sys.version.split()[0],
'deployment_id': DEPLOYMENT_ID,
}
if CENSUS_REPORTING_ENABLED and not ISOLATED_DEPLOYMENT and not DEBUG and 'test' not in sys.argv:
try:
# Report anonymous census data
requests.get(f'{CENSUS_URL}?{urlencode(CENSUS_PARAMS)}', timeout=3, proxies=HTTP_PROXIES)
except requests.exceptions.RequestException:
pass
# #

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -27,10 +27,10 @@
"bootstrap": "5.3.3", "bootstrap": "5.3.3",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"flatpickr": "4.6.13", "flatpickr": "4.6.13",
"gridstack": "11.2.0", "gridstack": "11.3.0",
"htmx.org": "1.9.12", "htmx.org": "1.9.12",
"query-string": "9.1.1", "query-string": "9.1.1",
"sass": "1.83.1", "sass": "1.83.4",
"tom-select": "2.4.1", "tom-select": "2.4.1",
"typeface-inter": "3.18.1", "typeface-inter": "3.18.1",
"typeface-roboto-mono": "1.1.13" "typeface-roboto-mono": "1.1.13"

View File

@@ -1905,10 +1905,10 @@ graphql@16.10.0:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.10.0.tgz#24c01ae0af6b11ea87bf55694429198aaa8e220c" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.10.0.tgz#24c01ae0af6b11ea87bf55694429198aaa8e220c"
integrity sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ== integrity sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==
gridstack@11.2.0: gridstack@11.3.0:
version "11.2.0" version "11.3.0"
resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-11.2.0.tgz#8977a6632c521260f064ef171b92c7a8df4f58a9" resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-11.3.0.tgz#b110c66bafc64c920fc54933e2c9df4f7b2cfffe"
integrity sha512-ajwUzd9spR8NXDxfJotHWq9WOYoDOV9o6UJR3ksevNz8cvXNxDtI9H/lC+RN6ijM2DexureLlsG0RpYjBZiOtg== integrity sha512-Z0eRovKcZTRTs3zetJwjO6CNwrgIy845WfOeZGk8ybpeMCE8fMA8tScyKU72Y2M6uGHkjgwnjflglvPiv+RcBQ==
has-bigints@^1.0.1, has-bigints@^1.0.2: has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2" version "1.0.2"
@@ -2667,10 +2667,10 @@ safe-regex-test@^1.0.3:
es-errors "^1.3.0" es-errors "^1.3.0"
is-regex "^1.1.4" is-regex "^1.1.4"
sass@1.83.1: sass@1.83.4:
version "1.83.1" version "1.83.4"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.1.tgz#dee1ab94b47a6f9993d3195d36f556bcbda64846" resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.4.tgz#5ccf60f43eb61eeec300b780b8dcb85f16eec6d1"
integrity sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA== integrity sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA==
dependencies: dependencies:
chokidar "^4.0.0" chokidar "^4.0.0"
immutable "^5.0.2" immutable "^5.0.2"

View File

@@ -1,3 +1,3 @@
version: "4.2.1" version: "4.2.2"
edition: "Community" edition: "Community"
published: "2025-01-08" published: "2025-01-17"

View File

@@ -103,7 +103,7 @@
<tr> <tr>
<th scope="row" class="border-0 ps-3">{% trans "Protection rules" %}</th> <th scope="row" class="border-0 ps-3">{% trans "Protection rules" %}</th>
{% if config.PROTECTION_RULES %} {% if config.PROTECTION_RULES %}
<td class="border-0"><pre>{{ config.PROTECTION_RULES|json }}</pre></td> <td class="border-0"><pre>{{ config.PROTECTION_RULES }}</pre></td>
{% else %} {% else %}
<td class="border-0">{{ ''|placeholder }}</td> <td class="border-0">{{ ''|placeholder }}</td>
{% endif %} {% endif %}

View File

@@ -5,7 +5,7 @@
<div class="list-group-item px-1 py-2"> <div class="list-group-item px-1 py-2">
<a href="{{ entry.link }}" class="text-body">{{ entry.title }}</a> <a href="{{ entry.link }}" class="text-body">{{ entry.title }}</a>
<div class="text-secondary"> <div class="text-secondary">
{{ entry.summary|safe }} {{ entry.summary }}
</div> </div>
</div> </div>
{% empty %} {% empty %}

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: 2025-01-08 05:02+0000\n" "POT-Creation-Date: 2025-01-10 05:01+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"
@@ -766,8 +766,8 @@ msgstr ""
#: netbox/ipam/forms/filtersets.py:212 netbox/ipam/forms/filtersets.py:284 #: netbox/ipam/forms/filtersets.py:212 netbox/ipam/forms/filtersets.py:284
#: netbox/ipam/forms/filtersets.py:358 netbox/ipam/forms/filtersets.py:542 #: netbox/ipam/forms/filtersets.py:358 netbox/ipam/forms/filtersets.py:542
#: netbox/ipam/forms/model_forms.py:503 netbox/ipam/tables/ip.py:183 #: netbox/ipam/forms/model_forms.py:503 netbox/ipam/tables/ip.py:183
#: netbox/ipam/tables/ip.py:262 netbox/ipam/tables/ip.py:313 #: netbox/ipam/tables/ip.py:263 netbox/ipam/tables/ip.py:314
#: netbox/ipam/tables/ip.py:376 netbox/ipam/tables/ip.py:403 #: netbox/ipam/tables/ip.py:377 netbox/ipam/tables/ip.py:404
#: netbox/ipam/tables/vlans.py:95 netbox/ipam/tables/vlans.py:208 #: netbox/ipam/tables/vlans.py:95 netbox/ipam/tables/vlans.py:208
#: netbox/templates/circuits/circuit.html:34 #: netbox/templates/circuits/circuit.html:34
#: netbox/templates/circuits/virtualcircuit.html:43 #: netbox/templates/circuits/virtualcircuit.html:43
@@ -844,7 +844,7 @@ msgstr ""
#: netbox/ipam/forms/filtersets.py:122 netbox/ipam/forms/filtersets.py:145 #: netbox/ipam/forms/filtersets.py:122 netbox/ipam/forms/filtersets.py:145
#: netbox/ipam/forms/filtersets.py:176 netbox/ipam/forms/filtersets.py:270 #: netbox/ipam/forms/filtersets.py:176 netbox/ipam/forms/filtersets.py:270
#: netbox/ipam/forms/filtersets.py:313 netbox/ipam/forms/filtersets.py:510 #: netbox/ipam/forms/filtersets.py:313 netbox/ipam/forms/filtersets.py:510
#: netbox/ipam/tables/ip.py:406 netbox/ipam/tables/vlans.py:205 #: netbox/ipam/tables/ip.py:407 netbox/ipam/tables/vlans.py:205
#: netbox/templates/circuits/circuit.html:48 #: netbox/templates/circuits/circuit.html:48
#: netbox/templates/circuits/circuitgroup.html:36 #: netbox/templates/circuits/circuitgroup.html:36
#: netbox/templates/circuits/virtualcircuit.html:47 #: netbox/templates/circuits/virtualcircuit.html:47
@@ -1078,8 +1078,8 @@ msgstr ""
#: netbox/ipam/forms/filtersets.py:292 netbox/ipam/forms/filtersets.py:363 #: netbox/ipam/forms/filtersets.py:292 netbox/ipam/forms/filtersets.py:363
#: netbox/ipam/forms/filtersets.py:550 netbox/ipam/forms/model_forms.py:194 #: netbox/ipam/forms/filtersets.py:550 netbox/ipam/forms/model_forms.py:194
#: netbox/ipam/forms/model_forms.py:220 netbox/ipam/forms/model_forms.py:251 #: netbox/ipam/forms/model_forms.py:220 netbox/ipam/forms/model_forms.py:251
#: netbox/ipam/forms/model_forms.py:678 netbox/ipam/tables/ip.py:207 #: netbox/ipam/forms/model_forms.py:678 netbox/ipam/tables/ip.py:208
#: netbox/ipam/tables/ip.py:266 netbox/ipam/tables/ip.py:317 #: netbox/ipam/tables/ip.py:267 netbox/ipam/tables/ip.py:318
#: netbox/ipam/tables/vlans.py:99 netbox/ipam/tables/vlans.py:211 #: netbox/ipam/tables/vlans.py:99 netbox/ipam/tables/vlans.py:211
#: netbox/templates/circuits/virtualcircuittermination.html:42 #: netbox/templates/circuits/virtualcircuittermination.html:42
#: netbox/templates/dcim/device.html:182 #: netbox/templates/dcim/device.html:182
@@ -1191,7 +1191,7 @@ msgstr ""
#: netbox/dcim/tables/connections.py:65 netbox/dcim/tables/devices.py:1140 #: netbox/dcim/tables/connections.py:65 netbox/dcim/tables/devices.py:1140
#: netbox/ipam/forms/bulk_import.py:317 netbox/ipam/forms/model_forms.py:282 #: netbox/ipam/forms/bulk_import.py:317 netbox/ipam/forms/model_forms.py:282
#: netbox/ipam/forms/model_forms.py:291 netbox/ipam/tables/fhrp.py:64 #: netbox/ipam/forms/model_forms.py:291 netbox/ipam/tables/fhrp.py:64
#: netbox/ipam/tables/ip.py:322 netbox/ipam/tables/vlans.py:145 #: netbox/ipam/tables/ip.py:323 netbox/ipam/tables/vlans.py:145
#: netbox/templates/circuits/inc/circuit_termination_fields.html:52 #: netbox/templates/circuits/inc/circuit_termination_fields.html:52
#: netbox/templates/circuits/virtualcircuittermination.html:53 #: netbox/templates/circuits/virtualcircuittermination.html:53
#: netbox/templates/circuits/virtualcircuittermination.html:60 #: netbox/templates/circuits/virtualcircuittermination.html:60
@@ -1831,8 +1831,8 @@ msgstr ""
#: netbox/dcim/tables/racks.py:224 netbox/dcim/tables/sites.py:108 #: netbox/dcim/tables/racks.py:224 netbox/dcim/tables/sites.py:108
#: netbox/extras/tables/tables.py:582 netbox/ipam/tables/asn.py:69 #: netbox/extras/tables/tables.py:582 netbox/ipam/tables/asn.py:69
#: netbox/ipam/tables/fhrp.py:34 netbox/ipam/tables/ip.py:82 #: netbox/ipam/tables/fhrp.py:34 netbox/ipam/tables/ip.py:82
#: netbox/ipam/tables/ip.py:224 netbox/ipam/tables/ip.py:279 #: netbox/ipam/tables/ip.py:225 netbox/ipam/tables/ip.py:280
#: netbox/ipam/tables/ip.py:347 netbox/ipam/tables/services.py:24 #: netbox/ipam/tables/ip.py:348 netbox/ipam/tables/services.py:24
#: netbox/ipam/tables/services.py:54 netbox/ipam/tables/vlans.py:121 #: netbox/ipam/tables/services.py:54 netbox/ipam/tables/vlans.py:121
#: netbox/ipam/tables/vrfs.py:47 netbox/ipam/tables/vrfs.py:72 #: netbox/ipam/tables/vrfs.py:47 netbox/ipam/tables/vrfs.py:72
#: netbox/templates/dcim/htmx/cable_edit.html:89 #: netbox/templates/dcim/htmx/cable_edit.html:89
@@ -3009,7 +3009,7 @@ msgstr ""
#: netbox/dcim/tables/devices.py:689 netbox/dcim/tables/devices.py:899 #: netbox/dcim/tables/devices.py:689 netbox/dcim/tables/devices.py:899
#: netbox/dcim/tables/devices.py:986 netbox/dcim/tables/devices.py:1146 #: netbox/dcim/tables/devices.py:986 netbox/dcim/tables/devices.py:1146
#: netbox/extras/tables/tables.py:223 netbox/ipam/tables/fhrp.py:59 #: netbox/extras/tables/tables.py:223 netbox/ipam/tables/fhrp.py:59
#: netbox/ipam/tables/ip.py:328 netbox/ipam/tables/services.py:44 #: netbox/ipam/tables/ip.py:329 netbox/ipam/tables/services.py:44
#: netbox/templates/dcim/interface.html:108 #: netbox/templates/dcim/interface.html:108
#: netbox/templates/dcim/interface.html:366 #: netbox/templates/dcim/interface.html:366
#: netbox/templates/dcim/location.html:41 netbox/templates/dcim/region.html:37 #: netbox/templates/dcim/location.html:41 netbox/templates/dcim/region.html:37
@@ -3688,8 +3688,8 @@ msgstr ""
#: netbox/ipam/forms/model_forms.py:480 netbox/ipam/forms/model_forms.py:494 #: netbox/ipam/forms/model_forms.py:480 netbox/ipam/forms/model_forms.py:494
#: netbox/ipam/models/ip.py:217 netbox/ipam/models/ip.py:498 #: netbox/ipam/models/ip.py:217 netbox/ipam/models/ip.py:498
#: netbox/ipam/models/ip.py:719 netbox/ipam/models/vrfs.py:61 #: netbox/ipam/models/ip.py:719 netbox/ipam/models/vrfs.py:61
#: netbox/ipam/tables/ip.py:188 netbox/ipam/tables/ip.py:259 #: netbox/ipam/tables/ip.py:188 netbox/ipam/tables/ip.py:260
#: netbox/ipam/tables/ip.py:310 netbox/ipam/tables/ip.py:400 #: netbox/ipam/tables/ip.py:311 netbox/ipam/tables/ip.py:401
#: netbox/templates/dcim/interface.html:152 #: netbox/templates/dcim/interface.html:152
#: netbox/templates/ipam/ipaddress.html:18 #: netbox/templates/ipam/ipaddress.html:18
#: netbox/templates/ipam/iprange.html:40 netbox/templates/ipam/prefix.html:19 #: netbox/templates/ipam/iprange.html:40 netbox/templates/ipam/prefix.html:19
@@ -6994,8 +6994,8 @@ msgstr ""
#: netbox/dcim/tables/devices.py:197 netbox/dcim/tables/devices.py:1099 #: netbox/dcim/tables/devices.py:197 netbox/dcim/tables/devices.py:1099
#: netbox/ipam/forms/bulk_import.py:562 netbox/ipam/forms/model_forms.py:308 #: netbox/ipam/forms/bulk_import.py:562 netbox/ipam/forms/model_forms.py:308
#: netbox/ipam/forms/model_forms.py:321 netbox/ipam/tables/ip.py:306 #: netbox/ipam/forms/model_forms.py:321 netbox/ipam/tables/ip.py:307
#: netbox/ipam/tables/ip.py:373 netbox/ipam/tables/ip.py:396 #: netbox/ipam/tables/ip.py:374 netbox/ipam/tables/ip.py:397
#: netbox/templates/ipam/ipaddress.html:11 #: netbox/templates/ipam/ipaddress.html:11
#: netbox/virtualization/tables/virtualmachines.py:65 #: netbox/virtualization/tables/virtualmachines.py:65
msgid "IP Address" msgid "IP Address"
@@ -9658,7 +9658,7 @@ msgstr ""
#: netbox/ipam/forms/bulk_edit.py:218 netbox/ipam/forms/bulk_import.py:181 #: netbox/ipam/forms/bulk_edit.py:218 netbox/ipam/forms/bulk_import.py:181
#: netbox/ipam/forms/filtersets.py:259 netbox/ipam/forms/model_forms.py:217 #: netbox/ipam/forms/filtersets.py:259 netbox/ipam/forms/model_forms.py:217
#: netbox/ipam/models/vlans.py:272 netbox/ipam/tables/ip.py:204 #: netbox/ipam/models/vlans.py:272 netbox/ipam/tables/ip.py:205
#: netbox/templates/ipam/prefix.html:56 netbox/templates/ipam/vlan.html:12 #: netbox/templates/ipam/prefix.html:56 netbox/templates/ipam/vlan.html:12
#: netbox/templates/ipam/vlan/base.html:6 #: netbox/templates/ipam/vlan/base.html:6
#: netbox/templates/ipam/vlan_edit.html:10 #: netbox/templates/ipam/vlan_edit.html:10
@@ -10516,8 +10516,8 @@ msgstr ""
msgid "Prefixes" msgid "Prefixes"
msgstr "" msgstr ""
#: netbox/ipam/tables/ip.py:77 netbox/ipam/tables/ip.py:219 #: netbox/ipam/tables/ip.py:77 netbox/ipam/tables/ip.py:220
#: netbox/ipam/tables/ip.py:274 netbox/ipam/tables/vlans.py:55 #: netbox/ipam/tables/ip.py:275 netbox/ipam/tables/vlans.py:55
#: netbox/templates/dcim/device.html:260 #: netbox/templates/dcim/device.html:260
#: netbox/templates/ipam/aggregate.html:24 #: netbox/templates/ipam/aggregate.html:24
#: netbox/templates/ipam/iprange.html:29 netbox/templates/ipam/prefix.html:102 #: netbox/templates/ipam/iprange.html:29 netbox/templates/ipam/prefix.html:102
@@ -10542,31 +10542,31 @@ msgstr ""
msgid "Scope Type" msgid "Scope Type"
msgstr "" msgstr ""
#: netbox/ipam/tables/ip.py:211 #: netbox/ipam/tables/ip.py:212
msgid "Pool" msgid "Pool"
msgstr "" msgstr ""
#: netbox/ipam/tables/ip.py:215 netbox/ipam/tables/ip.py:270 #: netbox/ipam/tables/ip.py:216 netbox/ipam/tables/ip.py:271
msgid "Marked Utilized" msgid "Marked Utilized"
msgstr "" msgstr ""
#: netbox/ipam/tables/ip.py:254 #: netbox/ipam/tables/ip.py:255
msgid "Start address" msgid "Start address"
msgstr "" msgstr ""
#: netbox/ipam/tables/ip.py:333 #: netbox/ipam/tables/ip.py:334
msgid "NAT (Inside)" msgid "NAT (Inside)"
msgstr "" msgstr ""
#: netbox/ipam/tables/ip.py:338 #: netbox/ipam/tables/ip.py:339
msgid "NAT (Outside)" msgid "NAT (Outside)"
msgstr "" msgstr ""
#: netbox/ipam/tables/ip.py:343 #: netbox/ipam/tables/ip.py:344
msgid "Assigned" msgid "Assigned"
msgstr "" msgstr ""
#: netbox/ipam/tables/ip.py:379 netbox/templates/vpn/l2vpntermination.html:16 #: netbox/ipam/tables/ip.py:380 netbox/templates/vpn/l2vpntermination.html:16
#: netbox/vpn/forms/filtersets.py:240 #: netbox/vpn/forms/filtersets.py:240
msgid "Assigned Object" msgid "Assigned Object"
msgstr "" msgstr ""
@@ -11582,63 +11582,63 @@ msgstr ""
msgid "Cannot delete stores from registry" msgid "Cannot delete stores from registry"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:755 #: netbox/netbox/settings.py:742
msgid "Czech" msgid "Czech"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:756 #: netbox/netbox/settings.py:743
msgid "Danish" msgid "Danish"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:757 #: netbox/netbox/settings.py:744
msgid "German" msgid "German"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:758 #: netbox/netbox/settings.py:745
msgid "English" msgid "English"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:759 #: netbox/netbox/settings.py:746
msgid "Spanish" msgid "Spanish"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:760 #: netbox/netbox/settings.py:747
msgid "French" msgid "French"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:761 #: netbox/netbox/settings.py:748
msgid "Italian" msgid "Italian"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:762 #: netbox/netbox/settings.py:749
msgid "Japanese" msgid "Japanese"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:763 #: netbox/netbox/settings.py:750
msgid "Dutch" msgid "Dutch"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:764 #: netbox/netbox/settings.py:751
msgid "Polish" msgid "Polish"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:765 #: netbox/netbox/settings.py:752
msgid "Portuguese" msgid "Portuguese"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:766 #: netbox/netbox/settings.py:753
msgid "Russian" msgid "Russian"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:767 #: netbox/netbox/settings.py:754
msgid "Turkish" msgid "Turkish"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:768 #: netbox/netbox/settings.py:755
msgid "Ukrainian" msgid "Ukrainian"
msgstr "" msgstr ""
#: netbox/netbox/settings.py:769 #: netbox/netbox/settings.py:756
msgid "Chinese" msgid "Chinese"
msgstr "" msgstr ""

View File

@@ -12,8 +12,8 @@
# Alexander Ryazanov (alryaz) <alryaz@xavux.com>, 2024 # Alexander Ryazanov (alryaz) <alryaz@xavux.com>, 2024
# Vladyslav V. Prodan, 2024 # Vladyslav V. Prodan, 2024
# Jeremy Stretch, 2024 # Jeremy Stretch, 2024
# Michail Tatarinov, 2024
# Artem Kotik, 2025 # Artem Kotik, 2025
# Michail Tatarinov, 2025
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
@@ -22,7 +22,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-04 05:02+0000\n" "POT-Creation-Date: 2025-01-04 05:02+0000\n"
"PO-Revision-Date: 2023-10-30 17:48+0000\n" "PO-Revision-Date: 2023-10-30 17:48+0000\n"
"Last-Translator: Artem Kotik, 2025\n" "Last-Translator: Michail Tatarinov, 2025\n"
"Language-Team: Russian (https://app.transifex.com/netbox-community/teams/178115/ru/)\n" "Language-Team: Russian (https://app.transifex.com/netbox-community/teams/178115/ru/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -6334,7 +6334,7 @@ msgstr "распределительный щит"
#: netbox/dcim/models/power.py:56 #: netbox/dcim/models/power.py:56
msgid "power panels" msgid "power panels"
msgstr "распределительный щиты" msgstr "распределительные щиты"
#: netbox/dcim/models/power.py:70 #: netbox/dcim/models/power.py:70
#, python-brace-format #, python-brace-format
@@ -9256,7 +9256,7 @@ msgstr "SLAAC"
#: netbox/ipam/choices.py:89 #: netbox/ipam/choices.py:89
msgid "Loopback" msgid "Loopback"
msgstr "Обратная петля" msgstr "Loopback"
#: netbox/ipam/choices.py:91 #: netbox/ipam/choices.py:91
msgid "Anycast" msgid "Anycast"
@@ -11185,7 +11185,7 @@ msgstr "Сети провайдеров"
#: netbox/netbox/navigation/menu.py:298 #: netbox/netbox/navigation/menu.py:298
msgid "Power Panels" msgid "Power Panels"
msgstr "Распределительный щиты" msgstr "Распределительные щиты"
#: netbox/netbox/navigation/menu.py:309 #: netbox/netbox/navigation/menu.py:309
msgid "Configurations" msgid "Configurations"

View File

@@ -1,4 +1,5 @@
from django import forms from django import forms
from django.apps import apps
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@@ -143,17 +144,24 @@ class ClusterAddDevicesForm(forms.Form):
def clean(self): def clean(self):
super().clean() super().clean()
# If the Cluster is assigned to a Site, all Devices must be assigned to that Site. # If the Cluster is assigned to a Site or Location, all Devices must be assigned to that same scope.
if self.cluster.site is not None: if self.cluster.scope is not None:
for device in self.cleaned_data.get('devices', []): for device in self.cleaned_data.get('devices', []):
if device.site != self.cluster.site: for scope_field in ['site', 'location']:
device_scope = getattr(device, scope_field)
if (
self.cluster.scope_type.model_class() == apps.get_model('dcim', scope_field)
and device_scope != self.cluster.scope
):
raise ValidationError({ raise ValidationError({
'devices': _( 'devices': _(
"{device} belongs to a different site ({device_site}) than the cluster ({cluster_site})" "{device} belongs to a different {scope_field} ({device_scope}) than the "
"cluster ({cluster_scope})"
).format( ).format(
device=device, device=device,
device_site=device.site, scope_field=scope_field,
cluster_site=self.cluster.site device_scope=device_scope,
cluster_scope=self.cluster.scope
) )
}) })

View File

@@ -78,7 +78,8 @@ class ClusterTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
) )
scope = tables.Column( scope = tables.Column(
verbose_name=_('Scope'), verbose_name=_('Scope'),
linkify=True linkify=True,
orderable=False
) )
device_count = columns.LinkedCountColumn( device_count = columns.LinkedCountColumn(
viewname='dcim:device_list', viewname='dcim:device_list',

View File

@@ -56,7 +56,8 @@ class WirelessLANTable(TenancyColumnsMixin, NetBoxTable):
) )
scope = tables.Column( scope = tables.Column(
verbose_name=_('Scope'), verbose_name=_('Scope'),
linkify=True linkify=True,
orderable=False
) )
interface_count = tables.Column( interface_count = tables.Column(
verbose_name=_('Interfaces') verbose_name=_('Interfaces')

View File

@@ -1,6 +1,6 @@
Django==5.1.4 Django==5.1.5
django-cors-headers==4.6.0 django-cors-headers==4.6.0
django-debug-toolbar==4.4.6 django-debug-toolbar==5.0.1
django-filter==24.3 django-filter==24.3
django-htmx==1.21.0 django-htmx==1.21.0
django-graphiql-debug-toolbar==0.2.0 django-graphiql-debug-toolbar==0.2.0
@@ -12,7 +12,7 @@ django-rich==1.13.0
django-rq==3.0 django-rq==3.0
django-taggit==6.1.0 django-taggit==6.1.0
django-tables2==2.7.5 django-tables2==2.7.5
django-timezone-field==7.0 django-timezone-field==7.1
djangorestframework==3.15.2 djangorestframework==3.15.2
drf-spectacular==0.28.0 drf-spectacular==0.28.0
drf-spectacular-sidecar==2024.12.1 drf-spectacular-sidecar==2024.12.1
@@ -25,13 +25,13 @@ mkdocstrings[python-legacy]==0.27.0
netaddr==1.3.0 netaddr==1.3.0
nh3==0.2.20 nh3==0.2.20
Pillow==11.1.0 Pillow==11.1.0
psycopg[c,pool]==3.2.3 psycopg[c,pool]==3.2.4
PyYAML==6.0.2 PyYAML==6.0.2
requests==2.32.3 requests==2.32.3
rq==2.1.0 rq==2.1.0
social-auth-app-django==5.4.2 social-auth-app-django==5.4.2
social-auth-core==4.5.4 social-auth-core==4.5.4
strawberry-graphql==0.256.1 strawberry-graphql==0.258.0
strawberry-graphql-django==0.52.0 strawberry-graphql-django==0.52.0
svgwrite==1.4.3 svgwrite==1.4.3
tablib==3.7.0 tablib==3.7.0

View File

@@ -1,11 +1,6 @@
#!/bin/sh #!/bin/sh
# Create a link to this file at .git/hooks/pre-commit to # TODO: Remove this file in NetBox v4.3
# force PEP8 validation prior to committing # This script has been maintained to ease transition to the pre-commit tool.
#
# Ignored violations:
#
# W504: Line break after binary operator
# E501: Line too long
exec 1>&2 exec 1>&2
@@ -14,48 +9,8 @@ RED='\033[0;31m'
YELLOW='\033[0;33m' YELLOW='\033[0;33m'
NOCOLOR='\033[0m' NOCOLOR='\033[0m'
printf "${YELLOW}This script is obsolete and will be removed in a future release.\n" printf "${YELLOW}The pre-commit hook script is obsolete. Please use pre-commit instead:${NOCOLOR}\n"
printf "Please use pre-commit instead:\n"
printf " pip install pre-commit\n" printf " pip install pre-commit\n"
printf " pre-commit install${NOCOLOR}\n" printf " pre-commit install${NOCOLOR}\n"
if [ -d ./venv/ ]; then exit 1
VENV="$PWD/venv"
if [ -e $VENV/bin/python ]; then
PATH=$VENV/bin:$PATH
elif [ -e $VENV/Scripts/python.exe ]; then
PATH=$VENV/Scripts:$PATH
fi
fi
if [ ${NOVALIDATE} ]; then
echo "${YELLOW}Skipping validation checks${NOCOLOR}"
exit $EXIT
fi
echo "Linting with ruff..."
ruff check netbox/
if [ $? != 0 ]; then
EXIT=1
fi
echo "Checking for missing migrations..."
python netbox/manage.py makemigrations --dry-run --check
if [ $? != 0 ]; then
EXIT=1
fi
git diff --cached --name-only | if grep --quiet 'netbox/project-static/'
then
echo "Checking UI ESLint, TypeScript, and Prettier compliance..."
yarn --cwd "$PWD/netbox/project-static" validate
if [ $? != 0 ]; then
EXIT=1
fi
fi
if [ $EXIT != 0 ]; then
printf "${RED}COMMIT FAILED${NOCOLOR}\n"
fi
exit $EXIT