From 5ae435cb43fad1f6ccd26772521fbc86deced5f7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 31 Jul 2025 15:58:28 -0400 Subject: [PATCH] Fixes #19987: Show changelog_message field only for models which support change logging --- netbox/dcim/forms/bulk_edit.py | 4 +-- netbox/dcim/forms/model_forms.py | 4 +-- netbox/extras/forms/bulk_edit.py | 22 ++++++++-------- netbox/extras/forms/model_forms.py | 20 +++++++------- netbox/netbox/forms/base.py | 6 ++--- netbox/netbox/forms/mixins.py | 4 +-- netbox/netbox/views/generic/bulk_views.py | 19 +++----------- netbox/netbox/views/generic/object_views.py | 4 +-- netbox/utilities/forms/bulk_import.py | 4 +-- netbox/utilities/forms/forms.py | 29 +++++++++++++++++++++ 10 files changed, 66 insertions(+), 50 deletions(-) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 4cca47782..5f70683ae 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -11,7 +11,7 @@ from ipam.choices import VLANQinQRoleChoices from ipam.models import ASN, VLAN, VLANGroup, VRF from netbox.choices import * from netbox.forms import NetBoxModelBulkEditForm -from netbox.forms.mixins import ChangeLoggingMixin +from netbox.forms.mixins import ChangelogMessageMixin from tenancy.models import Tenant from users.models import User from utilities.forms import BulkEditForm, add_blank_choice, form_from_model @@ -1038,7 +1038,7 @@ class PowerFeedBulkEditForm(NetBoxModelBulkEditForm): # Device component templates # -class ComponentTemplateBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class ComponentTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm): pass diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 55fca4efa..6454e1d14 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -11,7 +11,7 @@ from extras.models import ConfigTemplate from ipam.choices import VLANQinQRoleChoices from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF from netbox.forms import NetBoxModelForm -from netbox.forms.mixins import ChangeLoggingMixin +from netbox.forms.mixins import ChangelogMessageMixin from tenancy.forms import TenancyForm from users.models import User from utilities.forms import add_blank_choice, get_field_value @@ -974,7 +974,7 @@ class VCMemberSelectForm(forms.Form): # Device component templates # -class ComponentTemplateForm(ChangeLoggingMixin, forms.ModelForm): +class ComponentTemplateForm(ChangelogMessageMixin, forms.ModelForm): device_type = DynamicModelChoiceField( label=_('Device type'), queryset=DeviceType.objects.all(), diff --git a/netbox/extras/forms/bulk_edit.py b/netbox/extras/forms/bulk_edit.py index 1afc8a0f2..258910a8c 100644 --- a/netbox/extras/forms/bulk_edit.py +++ b/netbox/extras/forms/bulk_edit.py @@ -5,7 +5,7 @@ from extras.choices import * from extras.models import * from netbox.events import get_event_type_choices from netbox.forms import NetBoxModelBulkEditForm -from netbox.forms.mixins import ChangeLoggingMixin +from netbox.forms.mixins import ChangelogMessageMixin from utilities.forms import BulkEditForm, add_blank_choice from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField from utilities.forms.rendering import FieldSet @@ -28,7 +28,7 @@ __all__ = ( ) -class CustomFieldBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class CustomFieldBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=CustomField.objects.all(), widget=forms.MultipleHiddenInput @@ -96,7 +96,7 @@ class CustomFieldBulkEditForm(ChangeLoggingMixin, BulkEditForm): nullable_fields = ('group_name', 'description', 'choice_set') -class CustomFieldChoiceSetBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class CustomFieldChoiceSetBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=CustomFieldChoiceSet.objects.all(), widget=forms.MultipleHiddenInput @@ -116,7 +116,7 @@ class CustomFieldChoiceSetBulkEditForm(ChangeLoggingMixin, BulkEditForm): nullable_fields = ('base_choices', 'description') -class CustomLinkBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class CustomLinkBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=CustomLink.objects.all(), widget=forms.MultipleHiddenInput @@ -142,7 +142,7 @@ class CustomLinkBulkEditForm(ChangeLoggingMixin, BulkEditForm): ) -class ExportTemplateBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class ExportTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=ExportTemplate.objects.all(), widget=forms.MultipleHiddenInput @@ -175,7 +175,7 @@ class ExportTemplateBulkEditForm(ChangeLoggingMixin, BulkEditForm): nullable_fields = ('description', 'mime_type', 'file_name', 'file_extension') -class SavedFilterBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class SavedFilterBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=SavedFilter.objects.all(), widget=forms.MultipleHiddenInput @@ -295,7 +295,7 @@ class EventRuleBulkEditForm(NetBoxModelBulkEditForm): nullable_fields = ('description', 'conditions') -class TagBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class TagBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Tag.objects.all(), widget=forms.MultipleHiddenInput @@ -317,7 +317,7 @@ class TagBulkEditForm(ChangeLoggingMixin, BulkEditForm): nullable_fields = ('description',) -class ConfigContextBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class ConfigContextBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=ConfigContext.objects.all(), widget=forms.MultipleHiddenInput @@ -341,7 +341,7 @@ class ConfigContextBulkEditForm(ChangeLoggingMixin, BulkEditForm): nullable_fields = ('description',) -class ConfigTemplateBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class ConfigTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=ConfigTemplate.objects.all(), widget=forms.MultipleHiddenInput @@ -374,7 +374,7 @@ class ConfigTemplateBulkEditForm(ChangeLoggingMixin, BulkEditForm): nullable_fields = ('description', 'mime_type', 'file_name', 'file_extension') -class JournalEntryBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class JournalEntryBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=JournalEntry.objects.all(), widget=forms.MultipleHiddenInput @@ -387,7 +387,7 @@ class JournalEntryBulkEditForm(ChangeLoggingMixin, BulkEditForm): comments = CommentField() -class NotificationGroupBulkEditForm(ChangeLoggingMixin, BulkEditForm): +class NotificationGroupBulkEditForm(ChangelogMessageMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=NotificationGroup.objects.all(), widget=forms.MultipleHiddenInput diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index cccef14fc..13499fc2e 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -13,7 +13,7 @@ from extras.choices import * from extras.models import * from netbox.events import get_event_type_choices from netbox.forms import NetBoxModelForm -from netbox.forms.mixins import ChangeLoggingMixin +from netbox.forms.mixins import ChangelogMessageMixin from tenancy.models import Tenant, TenantGroup from users.models import Group, User from utilities.forms import get_field_value @@ -46,7 +46,7 @@ __all__ = ( ) -class CustomFieldForm(ChangeLoggingMixin, forms.ModelForm): +class CustomFieldForm(ChangelogMessageMixin, forms.ModelForm): object_types = ContentTypeMultipleChoiceField( label=_('Object types'), queryset=ObjectType.objects.with_feature('custom_fields'), @@ -165,7 +165,7 @@ class CustomFieldForm(ChangeLoggingMixin, forms.ModelForm): del self.fields['choice_set'] -class CustomFieldChoiceSetForm(ChangeLoggingMixin, forms.ModelForm): +class CustomFieldChoiceSetForm(ChangelogMessageMixin, forms.ModelForm): # TODO: The extra_choices field definition diverge from the CustomFieldChoiceSet model extra_choices = forms.CharField( widget=ChoicesWidget(), @@ -218,7 +218,7 @@ class CustomFieldChoiceSetForm(ChangeLoggingMixin, forms.ModelForm): return data -class CustomLinkForm(ChangeLoggingMixin, forms.ModelForm): +class CustomLinkForm(ChangelogMessageMixin, forms.ModelForm): object_types = ContentTypeMultipleChoiceField( label=_('Object types'), queryset=ObjectType.objects.with_feature('custom_links') @@ -250,7 +250,7 @@ class CustomLinkForm(ChangeLoggingMixin, forms.ModelForm): } -class ExportTemplateForm(ChangeLoggingMixin, SyncedDataMixin, forms.ModelForm): +class ExportTemplateForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm): object_types = ContentTypeMultipleChoiceField( label=_('Object types'), queryset=ObjectType.objects.with_feature('export_templates') @@ -292,7 +292,7 @@ class ExportTemplateForm(ChangeLoggingMixin, SyncedDataMixin, forms.ModelForm): return self.cleaned_data -class SavedFilterForm(ChangeLoggingMixin, forms.ModelForm): +class SavedFilterForm(ChangelogMessageMixin, forms.ModelForm): slug = SlugField() object_types = ContentTypeMultipleChoiceField( label=_('Object types'), @@ -389,7 +389,7 @@ class BookmarkForm(forms.ModelForm): fields = ('object_type', 'object_id') -class NotificationGroupForm(ChangeLoggingMixin, forms.ModelForm): +class NotificationGroupForm(ChangelogMessageMixin, forms.ModelForm): groups = DynamicModelMultipleChoiceField( label=_('Groups'), required=False, @@ -562,7 +562,7 @@ class EventRuleForm(NetBoxModelForm): return self.cleaned_data -class TagForm(ChangeLoggingMixin, forms.ModelForm): +class TagForm(ChangelogMessageMixin, forms.ModelForm): slug = SlugField() object_types = ContentTypeMultipleChoiceField( label=_('Object types'), @@ -585,7 +585,7 @@ class TagForm(ChangeLoggingMixin, forms.ModelForm): ] -class ConfigContextForm(ChangeLoggingMixin, SyncedDataMixin, forms.ModelForm): +class ConfigContextForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm): regions = DynamicModelMultipleChoiceField( label=_('Regions'), queryset=Region.objects.all(), @@ -697,7 +697,7 @@ class ConfigContextForm(ChangeLoggingMixin, SyncedDataMixin, forms.ModelForm): return self.cleaned_data -class ConfigTemplateForm(ChangeLoggingMixin, SyncedDataMixin, forms.ModelForm): +class ConfigTemplateForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm): tags = DynamicModelMultipleChoiceField( label=_('Tags'), queryset=Tag.objects.all(), diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index 4b8f7027d..14916a733 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -11,7 +11,7 @@ from extras.models import CustomField, Tag from utilities.forms import BulkEditForm, CSVModelForm from utilities.forms.fields import CSVModelMultipleChoiceField, DynamicModelMultipleChoiceField from utilities.forms.mixins import CheckLastUpdatedMixin -from .mixins import ChangeLoggingMixin, CustomFieldsMixin, SavedFiltersMixin, TagsMixin +from .mixins import ChangelogMessageMixin, CustomFieldsMixin, SavedFiltersMixin, TagsMixin __all__ = ( 'NetBoxModelForm', @@ -21,7 +21,7 @@ __all__ = ( ) -class NetBoxModelForm(ChangeLoggingMixin, CheckLastUpdatedMixin, CustomFieldsMixin, TagsMixin, forms.ModelForm): +class NetBoxModelForm(ChangelogMessageMixin, CheckLastUpdatedMixin, CustomFieldsMixin, TagsMixin, forms.ModelForm): """ Base form for creating & editing NetBox models. Extends Django's ModelForm to add support for custom fields. @@ -100,7 +100,7 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm): return customfield.to_form_field(for_csv_import=True) -class NetBoxModelBulkEditForm(ChangeLoggingMixin, CustomFieldsMixin, BulkEditForm): +class NetBoxModelBulkEditForm(ChangelogMessageMixin, CustomFieldsMixin, BulkEditForm): """ Base form for modifying multiple NetBox objects (of the same type) in bulk via the UI. Adds support for custom fields and adding/removing tags. diff --git a/netbox/netbox/forms/mixins.py b/netbox/netbox/forms/mixins.py index 8ecca73e1..84928fcd9 100644 --- a/netbox/netbox/forms/mixins.py +++ b/netbox/netbox/forms/mixins.py @@ -7,14 +7,14 @@ from extras.models import * from utilities.forms.fields import DynamicModelMultipleChoiceField __all__ = ( - 'ChangeLoggingMixin', + 'ChangelogMessageMixin', 'CustomFieldsMixin', 'SavedFiltersMixin', 'TagsMixin', ) -class ChangeLoggingMixin(forms.Form): +class ChangelogMessageMixin(forms.Form): """ Adds an optional field for recording a message on the resulting changelog record(s). """ diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 5f0bb609c..fec4c1ec7 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -21,14 +21,12 @@ from core.models import ObjectType from core.signals import clear_events from extras.choices import CustomFieldUIEditableChoices from extras.models import CustomField, ExportTemplate -from netbox.forms.mixins import ChangeLoggingMixin from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename from utilities.error_handlers import handle_protectederror from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation from utilities.export import TableExport -from utilities.forms import BulkRenameForm, ConfirmationForm, restrict_form_fields +from utilities.forms import BulkDeleteForm, BulkRenameForm, restrict_form_fields from utilities.forms.bulk_import import BulkImportForm -from utilities.forms.mixins import BackgroundJobMixin from utilities.htmx import htmx_partial from utilities.jobs import AsyncJobData, is_background_request, process_request_as_job from utilities.permissions import get_permission_for_model @@ -896,15 +894,6 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView): def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'delete') - def get_form(self): - """ - Provide a standard bulk delete form if none has been specified for the view - """ - class BulkDeleteForm(BackgroundJobMixin, ChangeLoggingMixin, ConfirmationForm): - pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput) - - return BulkDeleteForm - # # Request handlers # @@ -925,10 +914,8 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView): else: pk_list = [int(pk) for pk in request.POST.getlist('pk')] - form_cls = self.get_form() - if '_confirm' in request.POST: - form = form_cls(request.POST) + form = BulkDeleteForm(model, request.POST) if form.is_valid(): logger.debug("Form validation was successful") @@ -990,7 +977,7 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView): logger.debug("Form validation failed") else: - form = form_cls(initial={ + form = BulkDeleteForm(model, initial={ 'pk': pk_list, 'return_url': self.get_return_url(request), }) diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 657f95f1f..efb1b58d0 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -425,7 +425,7 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView): request: The current request """ obj = self.get_object(**kwargs) - form = DeleteForm(initial=request.GET) + form = DeleteForm(instance=obj, initial=request.GET) try: dependent_objects = self._get_dependent_objects(obj) @@ -464,7 +464,7 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView): """ logger = logging.getLogger('netbox.views.ObjectDeleteView') obj = self.get_object(**kwargs) - form = DeleteForm(request.POST) + form = DeleteForm(request.POST, instance=obj) if form.is_valid(): logger.debug("Form validation was successful") diff --git a/netbox/utilities/forms/bulk_import.py b/netbox/utilities/forms/bulk_import.py index abebe255f..967ba1196 100644 --- a/netbox/utilities/forms/bulk_import.py +++ b/netbox/utilities/forms/bulk_import.py @@ -8,13 +8,13 @@ from django.utils.translation import gettext as _ from core.forms.mixins import SyncedDataMixin from netbox.choices import CSVDelimiterChoices, ImportFormatChoices, ImportMethodChoices -from netbox.forms.mixins import ChangeLoggingMixin +from netbox.forms.mixins import ChangelogMessageMixin from utilities.constants import CSV_DELIMITERS from utilities.forms.mixins import BackgroundJobMixin from utilities.forms.utils import parse_csv -class BulkImportForm(ChangeLoggingMixin, BackgroundJobMixin, SyncedDataMixin, forms.Form): +class BulkImportForm(ChangelogMessageMixin, BackgroundJobMixin, SyncedDataMixin, forms.Form): import_method = forms.ChoiceField( choices=ImportMethodChoices, required=False diff --git a/netbox/utilities/forms/forms.py b/netbox/utilities/forms/forms.py index 122107728..b7f86a94b 100644 --- a/netbox/utilities/forms/forms.py +++ b/netbox/utilities/forms/forms.py @@ -3,9 +3,11 @@ import re from django import forms from django.utils.translation import gettext as _ +from netbox.models.features import ChangeLoggingMixin from utilities.forms.mixins import BackgroundJobMixin __all__ = ( + 'BulkDeleteForm', 'BulkEditForm', 'BulkRenameForm', 'ConfirmationForm', @@ -40,6 +42,13 @@ class DeleteForm(ConfirmationForm): max_length=200 ) + def __init__(self, *args, instance=None, **kwargs): + super().__init__(*args, **kwargs) + + # Hide the changelog_message filed if the model doesn't support change logging + if instance is None or not issubclass(instance._meta.model, ChangeLoggingMixin): + self.fields.pop('changelog_message') + class BulkEditForm(BackgroundJobMixin, forms.Form): """ @@ -81,6 +90,26 @@ class BulkRenameForm(forms.Form): }) +class BulkDeleteForm(BackgroundJobMixin, ConfirmationForm): + pk = forms.ModelMultipleChoiceField( + queryset=None, + widget=forms.MultipleHiddenInput + ) + changelog_message = forms.CharField( + required=False, + max_length=200 + ) + + def __init__(self, model, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields['pk'].queryset = model.objects.all() + + # Hide the changelog_message filed if the model doesn't support change logging + if model is None or not issubclass(model, ChangeLoggingMixin): + self.fields.pop('changelog_message') + + class CSVModelForm(forms.ModelForm): """ ModelForm used for the import of objects in CSV format.