forms.py 11 KB

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