forms.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. from django import forms
  2. from django.contrib.auth.models import User
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.utils.safestring import mark_safe
  5. from dcim.models import DeviceRole, Platform, Region, Site
  6. from tenancy.models import Tenant, TenantGroup
  7. from utilities.forms import (
  8. add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
  9. ContentTypeSelect, CSVModelForm, DateTimePicker, DynamicModelMultipleChoiceField, JSONField, SlugField,
  10. StaticSelect2, BOOLEAN_WITH_BLANK_CHOICES,
  11. )
  12. from virtualization.models import Cluster, ClusterGroup
  13. from .choices import *
  14. from .models import ConfigContext, CustomField, ImageAttachment, ObjectChange, Tag
  15. #
  16. # Custom fields
  17. #
  18. class CustomFieldModelForm(forms.ModelForm):
  19. def __init__(self, *args, **kwargs):
  20. self.obj_type = ContentType.objects.get_for_model(self._meta.model)
  21. self.custom_fields = []
  22. self.custom_field_values = {}
  23. super().__init__(*args, **kwargs)
  24. if self.instance._cf is None:
  25. self.instance._cf = {}
  26. self._append_customfield_fields()
  27. def _append_customfield_fields(self):
  28. """
  29. Append form fields for all CustomFields assigned to this model.
  30. """
  31. # Retrieve initial CustomField values for the instance
  32. if self.instance.pk:
  33. self.custom_field_values = self.instance.custom_field_data
  34. # Append form fields; assign initial values if modifying and existing object
  35. for cf in CustomField.objects.filter(obj_type=self.obj_type):
  36. field_name = 'cf_{}'.format(cf.name)
  37. if self.instance.pk:
  38. self.fields[field_name] = cf.to_form_field(set_initial=False)
  39. value = self.custom_field_values.get(cf.name)
  40. self.fields[field_name].initial = value
  41. self.instance._cf[cf.name] = value
  42. else:
  43. self.fields[field_name] = cf.to_form_field()
  44. self.instance._cf[cf.name] = self.fields[field_name].initial
  45. # Annotate the field in the list of CustomField form fields
  46. self.custom_fields.append(field_name)
  47. def save(self, commit=True):
  48. # Save custom field data on instance
  49. for cf_name in self.custom_fields:
  50. self.instance.custom_field_data[cf_name[3:]] = self.cleaned_data.get(cf_name)
  51. return super().save(commit)
  52. class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelForm):
  53. def _append_customfield_fields(self):
  54. # Append form fields
  55. for cf in CustomField.objects.filter(obj_type=self.obj_type):
  56. field_name = 'cf_{}'.format(cf.name)
  57. self.fields[field_name] = cf.to_form_field(for_csv_import=True)
  58. # Annotate the field in the list of CustomField form fields
  59. self.custom_fields.append(field_name)
  60. class CustomFieldBulkEditForm(BulkEditForm):
  61. def __init__(self, *args, **kwargs):
  62. super().__init__(*args, **kwargs)
  63. self.custom_fields = []
  64. self.obj_type = ContentType.objects.get_for_model(self.model)
  65. # Add all applicable CustomFields to the form
  66. custom_fields = CustomField.objects.filter(obj_type=self.obj_type)
  67. for cf in custom_fields:
  68. # Annotate non-required custom fields as nullable
  69. if not cf.required:
  70. self.nullable_fields.append(cf.name)
  71. self.fields[cf.name] = cf.to_form_field(set_initial=False, enforce_required=False)
  72. # Annotate this as a custom field
  73. self.custom_fields.append(cf.name)
  74. class CustomFieldFilterForm(forms.Form):
  75. def __init__(self, *args, **kwargs):
  76. self.obj_type = ContentType.objects.get_for_model(self.model)
  77. super().__init__(*args, **kwargs)
  78. # Add all applicable CustomFields to the form
  79. custom_fields = CustomField.objects.filter(obj_type=self.obj_type).exclude(
  80. filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
  81. )
  82. for cf in custom_fields:
  83. field_name = 'cf_{}'.format(cf.name)
  84. self.fields[field_name] = cf.to_form_field(set_initial=True, enforce_required=False)
  85. #
  86. # Tags
  87. #
  88. class TagForm(BootstrapMixin, forms.ModelForm):
  89. slug = SlugField()
  90. class Meta:
  91. model = Tag
  92. fields = [
  93. 'name', 'slug', 'color', 'description'
  94. ]
  95. class TagCSVForm(CSVModelForm):
  96. slug = SlugField()
  97. class Meta:
  98. model = Tag
  99. fields = Tag.csv_headers
  100. help_texts = {
  101. 'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
  102. }
  103. class AddRemoveTagsForm(forms.Form):
  104. def __init__(self, *args, **kwargs):
  105. super().__init__(*args, **kwargs)
  106. # Add add/remove tags fields
  107. self.fields['add_tags'] = DynamicModelMultipleChoiceField(
  108. queryset=Tag.objects.all(),
  109. required=False
  110. )
  111. self.fields['remove_tags'] = DynamicModelMultipleChoiceField(
  112. queryset=Tag.objects.all(),
  113. required=False
  114. )
  115. class TagFilterForm(BootstrapMixin, forms.Form):
  116. model = Tag
  117. q = forms.CharField(
  118. required=False,
  119. label='Search'
  120. )
  121. class TagBulkEditForm(BootstrapMixin, BulkEditForm):
  122. pk = forms.ModelMultipleChoiceField(
  123. queryset=Tag.objects.all(),
  124. widget=forms.MultipleHiddenInput
  125. )
  126. color = forms.CharField(
  127. max_length=6,
  128. required=False,
  129. widget=ColorSelect()
  130. )
  131. description = forms.CharField(
  132. max_length=200,
  133. required=False
  134. )
  135. class Meta:
  136. nullable_fields = ['description']
  137. #
  138. # Config contexts
  139. #
  140. class ConfigContextForm(BootstrapMixin, forms.ModelForm):
  141. regions = DynamicModelMultipleChoiceField(
  142. queryset=Region.objects.all(),
  143. required=False
  144. )
  145. sites = DynamicModelMultipleChoiceField(
  146. queryset=Site.objects.all(),
  147. required=False
  148. )
  149. roles = DynamicModelMultipleChoiceField(
  150. queryset=DeviceRole.objects.all(),
  151. required=False
  152. )
  153. platforms = DynamicModelMultipleChoiceField(
  154. queryset=Platform.objects.all(),
  155. required=False
  156. )
  157. cluster_groups = DynamicModelMultipleChoiceField(
  158. queryset=ClusterGroup.objects.all(),
  159. required=False
  160. )
  161. clusters = DynamicModelMultipleChoiceField(
  162. queryset=Cluster.objects.all(),
  163. required=False
  164. )
  165. tenant_groups = DynamicModelMultipleChoiceField(
  166. queryset=TenantGroup.objects.all(),
  167. required=False
  168. )
  169. tenants = DynamicModelMultipleChoiceField(
  170. queryset=Tenant.objects.all(),
  171. required=False
  172. )
  173. tags = DynamicModelMultipleChoiceField(
  174. queryset=Tag.objects.all(),
  175. required=False
  176. )
  177. data = JSONField(
  178. label=''
  179. )
  180. class Meta:
  181. model = ConfigContext
  182. fields = (
  183. 'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms', 'cluster_groups',
  184. 'clusters', 'tenant_groups', 'tenants', 'tags', 'data',
  185. )
  186. class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm):
  187. pk = forms.ModelMultipleChoiceField(
  188. queryset=ConfigContext.objects.all(),
  189. widget=forms.MultipleHiddenInput
  190. )
  191. weight = forms.IntegerField(
  192. required=False,
  193. min_value=0
  194. )
  195. is_active = forms.NullBooleanField(
  196. required=False,
  197. widget=BulkEditNullBooleanSelect()
  198. )
  199. description = forms.CharField(
  200. required=False,
  201. max_length=100
  202. )
  203. class Meta:
  204. nullable_fields = [
  205. 'description',
  206. ]
  207. class ConfigContextFilterForm(BootstrapMixin, forms.Form):
  208. q = forms.CharField(
  209. required=False,
  210. label='Search'
  211. )
  212. region = DynamicModelMultipleChoiceField(
  213. queryset=Region.objects.all(),
  214. to_field_name='slug',
  215. required=False
  216. )
  217. site = DynamicModelMultipleChoiceField(
  218. queryset=Site.objects.all(),
  219. to_field_name='slug',
  220. required=False
  221. )
  222. role = DynamicModelMultipleChoiceField(
  223. queryset=DeviceRole.objects.all(),
  224. to_field_name='slug',
  225. required=False
  226. )
  227. platform = DynamicModelMultipleChoiceField(
  228. queryset=Platform.objects.all(),
  229. to_field_name='slug',
  230. required=False
  231. )
  232. cluster_group = DynamicModelMultipleChoiceField(
  233. queryset=ClusterGroup.objects.all(),
  234. to_field_name='slug',
  235. required=False
  236. )
  237. cluster_id = DynamicModelMultipleChoiceField(
  238. queryset=Cluster.objects.all(),
  239. required=False,
  240. label='Cluster'
  241. )
  242. tenant_group = DynamicModelMultipleChoiceField(
  243. queryset=TenantGroup.objects.all(),
  244. to_field_name='slug',
  245. required=False
  246. )
  247. tenant = DynamicModelMultipleChoiceField(
  248. queryset=Tenant.objects.all(),
  249. to_field_name='slug',
  250. required=False
  251. )
  252. tag = DynamicModelMultipleChoiceField(
  253. queryset=Tag.objects.all(),
  254. to_field_name='slug',
  255. required=False
  256. )
  257. #
  258. # Filter form for local config context data
  259. #
  260. class LocalConfigContextFilterForm(forms.Form):
  261. local_context_data = forms.NullBooleanField(
  262. required=False,
  263. label='Has local config context data',
  264. widget=StaticSelect2(
  265. choices=BOOLEAN_WITH_BLANK_CHOICES
  266. )
  267. )
  268. #
  269. # Image attachments
  270. #
  271. class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
  272. class Meta:
  273. model = ImageAttachment
  274. fields = [
  275. 'name', 'image',
  276. ]
  277. #
  278. # Change logging
  279. #
  280. class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
  281. model = ObjectChange
  282. q = forms.CharField(
  283. required=False,
  284. label='Search'
  285. )
  286. time_after = forms.DateTimeField(
  287. label='After',
  288. required=False,
  289. widget=DateTimePicker()
  290. )
  291. time_before = forms.DateTimeField(
  292. label='Before',
  293. required=False,
  294. widget=DateTimePicker()
  295. )
  296. action = forms.ChoiceField(
  297. choices=add_blank_choice(ObjectChangeActionChoices),
  298. required=False,
  299. widget=StaticSelect2()
  300. )
  301. user = DynamicModelMultipleChoiceField(
  302. queryset=User.objects.all(),
  303. required=False,
  304. display_field='username',
  305. widget=APISelectMultiple(
  306. api_url='/api/users/users/',
  307. )
  308. )
  309. changed_object_type = forms.ModelChoiceField(
  310. queryset=ContentType.objects.order_by('model'),
  311. required=False,
  312. widget=ContentTypeSelect(),
  313. label='Object Type'
  314. )
  315. #
  316. # Scripts
  317. #
  318. class ScriptForm(BootstrapMixin, forms.Form):
  319. _commit = forms.BooleanField(
  320. required=False,
  321. initial=True,
  322. label="Commit changes",
  323. help_text="Commit changes to the database (uncheck for a dry-run)"
  324. )
  325. def __init__(self, *args, **kwargs):
  326. super().__init__(*args, **kwargs)
  327. # Move _commit to the end of the form
  328. commit = self.fields.pop('_commit')
  329. self.fields['_commit'] = commit
  330. @property
  331. def requires_input(self):
  332. """
  333. A boolean indicating whether the form requires user input (ignore the _commit field).
  334. """
  335. return bool(len(self.fields) > 1)