forms.py 5.1 KB

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