Compare commits

..

4 Commits

Author SHA1 Message Date
Arthur
24642be351 cleanup 2026-01-08 17:08:10 -08:00
Arthur
89af9efd85 cleanup 2026-01-08 17:04:00 -08:00
Arthur
99d678502f cleanup 2026-01-08 16:23:47 -08:00
Arthur
e6300ee06d Fix TomSelect dropdown ordering 2026-01-08 16:17:40 -08:00
12 changed files with 47 additions and 70 deletions

View File

@@ -1,26 +1,20 @@
--- ---
name: Deprecation name: 🗑 Deprecation
type: Deprecation type: Deprecation
description: Designation of a feature or behavior that will be removed in a future release description: The removal of an existing feature or resource
labels: ["netbox", "type: deprecation"] labels: ["netbox", "type: deprecation"]
body: body:
- type: textarea - type: textarea
attributes: attributes:
label: Deprecated Functionality label: Proposed Changes
description: > description: >
Describe the feature(s) and/or behavior that is being flagged for deprecation. Describe in detail the proposed changes. What is being removed?
validations:
required: true
- type: input
attributes:
label: Scheduled removal
description: In what future release will the deprecated functionality be removed?
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Justification label: Justification
description: Please provide justification for the deprecation. description: Please provide justification for the proposed change(s).
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@@ -1,20 +0,0 @@
---
name: 🗑️ Feature Removal
type: Removal
description: The removal of a deprecated feature or resource
labels: ["netbox", "type: removal"]
body:
- type: input
attributes:
label: Deprecation Issue
description: Specify the issue in which this deprecation was announced.
placeholder: "#1234"
validations:
required: true
- type: textarea
attributes:
label: Summary of Changes
description: >
List all changes necessary to remove the deprecated feature or resource.
validations:
required: true

3
.gitignore vendored
View File

@@ -9,8 +9,7 @@ yarn-error.log*
/netbox/netbox/configuration.py /netbox/netbox/configuration.py
/netbox/netbox/ldap_config.py /netbox/netbox/ldap_config.py
/netbox/local/* /netbox/local/*
/netbox/media/* /netbox/media
!/netbox/media/.gitkeep
/netbox/reports/* /netbox/reports/*
!/netbox/reports/__init__.py !/netbox/reports/__init__.py
/netbox/scripts/* /netbox/scripts/*

View File

@@ -733,9 +733,10 @@ class ModuleForm(ModuleCommonForm, PrimaryModelForm):
) )
module_bay = DynamicModelChoiceField( module_bay = DynamicModelChoiceField(
label=_('Module bay'), label=_('Module bay'),
queryset=ModuleBay.objects.all(), queryset=ModuleBay.objects.order_by('name'),
query_params={ query_params={
'device_id': '$device' 'device_id': '$device',
'ordering': 'name',
}, },
context={ context={
'disabled': 'installed_module', 'disabled': 'installed_module',

View File

View File

@@ -232,7 +232,7 @@ VPN_MENU = Menu(
label=_('L2VPNs'), label=_('L2VPNs'),
items=( items=(
get_model_item('vpn', 'l2vpn', _('L2VPNs')), get_model_item('vpn', 'l2vpn', _('L2VPNs')),
get_model_item('vpn', 'l2vpntermination', _('L2VPN Terminations')), get_model_item('vpn', 'l2vpntermination', _('Terminations')),
), ),
), ),
MenuGroup( MenuGroup(

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -75,11 +75,15 @@ export class DynamicTomSelect extends TomSelect {
load(value: string) { load(value: string) {
const self = this; const self = this;
const currentValue = self.getValue();
// Automatically clear any cached options. (Only options included // Automatically clear any cached options. (Only options included
// in the API response should be present.) // in the API response should be present.)
self.clearOptions(); self.clearOptions();
// Populate the null option (if any) if not searching // Clear user_options to prevent the pre-selected option from being treated specially
(self as any).user_options = {};
if (self.nullOption && !value) { if (self.nullOption && !value) {
self.addOption(self.nullOption); self.addOption(self.nullOption);
} }
@@ -93,21 +97,33 @@ export class DynamicTomSelect extends TomSelect {
addClasses(self.wrapper, self.settings.loadingClass); addClasses(self.wrapper, self.settings.loadingClass);
self.loading++; self.loading++;
// Make the API request
fetch(url) fetch(url)
.then(response => response.json()) .then(response => response.json())
.then(apiData => { .then(apiData => {
const results: Dict[] = apiData.results; const results: Dict[] = apiData.results;
const options: Dict[] = [];
for (const result of results) { // Add options and set $order to preserve API response order
results.forEach((result, index) => {
const option = self.getOptionFromData(result); const option = self.getOptionFromData(result);
options.push(option); self.addOption(option);
const key = option[self.settings.valueField as string] as string;
if (self.options[key]) {
(self.options[key] as any).$order = index;
}
});
if (self.loading > 0) {
self.loading--;
if (self.loading === 0) {
self.wrapper.classList.remove(self.settings.loadingClass as string);
}
} }
return options;
}) if (currentValue && !self.items.includes(currentValue as string)) {
// Pass the options to the callback function self.items.push(currentValue as string);
.then(options => { }
self.loadCallback(options, []);
self.refreshOptions(false);
}) })
.catch(() => { .catch(() => {
self.loadCallback([], []); self.loadCallback([], []);

View File

@@ -123,7 +123,7 @@ class UserTokenForm(forms.ModelForm):
token = forms.CharField( token = forms.CharField(
label=_('Token'), label=_('Token'),
help_text=_( help_text=_(
'Tokens must be at least 40 characters in length. <strong>Be sure to record your token</strong> prior to ' 'Tokens must be at least 40 characters in length. <strong>Be sure to record your key</strong> prior to '
'submitting this form, as it will no longer be accessible once the token has been created.' 'submitting this form, as it will no longer be accessible once the token has been created.'
), ),
widget=forms.TextInput( widget=forms.TextInput(

View File

@@ -69,7 +69,7 @@ class Token(models.Model):
write_enabled = models.BooleanField( write_enabled = models.BooleanField(
verbose_name=_('write enabled'), verbose_name=_('write enabled'),
default=True, default=True,
help_text=_('Permit create/update/delete operations using this token') help_text=_('Permit create/update/delete operations using this key')
) )
# For legacy v1 tokens, this field stores the plaintext 40-char token value. Not used for v2. # For legacy v1 tokens, this field stores the plaintext 40-char token value. Not used for v2.
plaintext = models.CharField( plaintext = models.CharField(
@@ -213,9 +213,6 @@ class Token(models.Model):
def clean(self): def clean(self):
super().clean() super().clean()
if self.version == TokenVersionChoices.V2 and not settings.API_TOKEN_PEPPERS:
raise ValidationError(_("Unable to save v2 tokens: API_TOKEN_PEPPERS is not defined."))
if self._state.adding: if self._state.adding:
if self.pepper_id is not None and self.pepper_id not in settings.API_TOKEN_PEPPERS: if self.pepper_id is not None and self.pepper_id not in settings.API_TOKEN_PEPPERS:
raise ValidationError(_( raise ValidationError(_(

View File

@@ -1,10 +1,9 @@
from datetime import timedelta from datetime import timedelta
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings from django.test import TestCase
from django.utils import timezone from django.utils import timezone
from users.choices import TokenVersionChoices
from users.models import User, Token from users.models import User, Token
from utilities.testing import create_test_user from utilities.testing import create_test_user
@@ -95,15 +94,6 @@ class TokenTest(TestCase):
token.refresh_from_db() token.refresh_from_db()
self.assertEqual(token.description, 'New Description') self.assertEqual(token.description, 'New Description')
@override_settings(API_TOKEN_PEPPERS={})
def test_v2_without_peppers_configured(self):
"""
Attempting to save a v2 token without API_TOKEN_PEPPERS defined should raise a ValidationError.
"""
token = Token(version=TokenVersionChoices.V2)
with self.assertRaises(ValidationError):
token.clean()
class UserConfigTest(TestCase): class UserConfigTest(TestCase):