Coverage for utilities/forms/forms.py: 45%
64 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1import re
3from django import forms
4from django.utils.translation import gettext as _
6from netbox.models.features import ChangeLoggingMixin
7from utilities.forms.fields import QueryField
8from utilities.forms.mixins import BackgroundJobMixin, FilterModifierMixin
10__all__ = (
11 'BulkDeleteForm',
12 'BulkEditForm',
13 'BulkRenameForm',
14 'CSVModelForm',
15 'ConfirmationForm',
16 'DeleteForm',
17 'FilterForm',
18 'TableConfigForm',
19)
22class ConfirmationForm(forms.Form):
23 """
24 A generic confirmation form. The form is not valid unless the `confirm` field is checked.
25 """
26 return_url = forms.CharField(
27 required=False,
28 widget=forms.HiddenInput()
29 )
30 confirm = forms.BooleanField(
31 required=True,
32 widget=forms.HiddenInput(),
33 initial=True
34 )
37class DeleteForm(ConfirmationForm):
38 """
39 Confirm the deletion of an object, optionally providing a changelog message.
40 """
41 changelog_message = forms.CharField(
42 required=False,
43 max_length=200
44 )
46 def __init__(self, *args, instance=None, **kwargs):
47 super().__init__(*args, **kwargs)
49 # Hide the changelog_message filed if the model doesn't support change logging
50 if instance is None or not issubclass(instance._meta.model, ChangeLoggingMixin):
51 self.fields.pop('changelog_message')
54class BulkEditForm(BackgroundJobMixin, forms.Form):
55 """
56 Provides bulk edit support for objects.
58 Attributes:
59 nullable_fields: A list of field names indicating which fields support being set to null/empty
60 """
61 nullable_fields = ()
64class BulkRenameForm(forms.Form):
65 """
66 An extendable form to be used for renaming objects in bulk.
67 """
68 find = forms.CharField(
69 strip=False
70 )
71 replace = forms.CharField(
72 strip=False,
73 required=False
74 )
75 use_regex = forms.BooleanField(
76 required=False,
77 initial=True,
78 label=_('Use regular expressions')
79 )
81 def clean(self):
82 super().clean()
84 # Validate regular expression in "find" field
85 if self.cleaned_data['use_regex']:
86 try:
87 re.compile(self.cleaned_data['find'])
88 except re.error:
89 raise forms.ValidationError({
90 'find': "Invalid regular expression"
91 })
94class BulkDeleteForm(BackgroundJobMixin, ConfirmationForm):
95 pk = forms.ModelMultipleChoiceField(
96 queryset=None,
97 widget=forms.MultipleHiddenInput
98 )
99 changelog_message = forms.CharField(
100 required=False,
101 max_length=200
102 )
104 def __init__(self, model, *args, **kwargs):
105 super().__init__(*args, **kwargs)
107 self.fields['pk'].queryset = model.objects.all()
109 # Hide the changelog_message filed if the model doesn't support change logging
110 if model is None or not issubclass(model, ChangeLoggingMixin):
111 self.fields.pop('changelog_message')
114class CSVModelForm(forms.ModelForm):
115 """
116 ModelForm used for the import of objects in CSV format.
117 """
118 id = forms.IntegerField(
119 label=_('ID'),
120 required=False,
121 help_text=_('Numeric ID of an existing object to update (if not creating a new object)')
122 )
124 def __init__(self, *args, headers=None, **kwargs):
125 self.headers = headers or {}
126 super().__init__(*args, **kwargs)
128 # Modify the model form to accommodate any customized to_field_name properties
129 for field, to_field in self.headers.items():
130 if to_field is not None:
131 self.fields[field].to_field_name = to_field
133 def clean(self):
134 # Flag any invalid CSV headers
135 for header in self.headers:
136 if header not in self.fields:
137 raise forms.ValidationError(
138 _("Unrecognized header: {name}").format(name=header)
139 )
141 return super().clean()
144class FilterForm(FilterModifierMixin, forms.Form):
145 """
146 Base Form class for FilterSet forms.
147 """
148 q = QueryField(
149 required=False,
150 label=_('Search')
151 )
154class TableConfigForm(forms.Form):
155 """
156 Form for configuring user's table preferences.
157 """
158 available_columns = forms.MultipleChoiceField(
159 choices=[],
160 required=False,
161 widget=forms.SelectMultiple(
162 attrs={'size': 10, 'class': 'form-select'}
163 ),
164 label=_('Available Columns')
165 )
166 columns = forms.MultipleChoiceField(
167 choices=[],
168 required=False,
169 widget=forms.SelectMultiple(
170 attrs={'size': 10, 'class': 'form-select select-all'}
171 ),
172 label=_('Selected Columns')
173 )
175 def __init__(self, table, *args, **kwargs):
176 self.table = table
178 super().__init__(*args, **kwargs)
180 # Initialize columns field based on table attributes
181 if table:
182 self.fields['available_columns'].choices = table.available_columns
183 self.fields['columns'].choices = table.selected_columns
185 @property
186 def table_name(self):
187 return self.table.__class__.__name__