Fixes #19987: Show changelog_message field only for models which support change logging

This commit is contained in:
Jeremy Stretch 2025-07-31 15:58:28 -04:00
parent 40dd36812c
commit 5ae435cb43
10 changed files with 66 additions and 50 deletions

View File

@ -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

View File

@ -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(),

View File

@ -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

View File

@ -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(),

View File

@ -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.

View File

@ -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).
"""

View File

@ -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),
})

View File

@ -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")

View File

@ -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

View File

@ -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.