Merge "Add bootstrap 4 support"
[pharos-tools.git] / dashboard / src / workflow / forms.py
1 ##############################################################################
2 # Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10
11 import django.forms as forms
12 from django.forms import widgets, ValidationError
13 from django.utils.safestring import mark_safe
14 from django.template.loader import render_to_string
15 from django.forms.widgets import NumberInput
16
17 import json
18
19 from account.models import Lab
20 from account.models import UserProfile
21 from resource_inventory.models import (
22     OPNFVRole,
23     Installer,
24     Scenario,
25 )
26 from booking.lib import get_user_items, get_user_field_opts
27
28
29 class SearchableSelectMultipleWidget(widgets.SelectMultiple):
30     template_name = 'dashboard/searchable_select_multiple.html'
31
32     def __init__(self, attrs=None):
33         self.items = attrs['items']
34         self.show_from_noentry = attrs['show_from_noentry']
35         self.show_x_results = attrs['show_x_results']
36         self.results_scrollable = attrs['results_scrollable']
37         self.selectable_limit = attrs['selectable_limit']
38         self.placeholder = attrs['placeholder']
39         self.name = attrs['name']
40         self.initial = attrs.get("initial", [])
41
42         super(SearchableSelectMultipleWidget, self).__init__()
43
44     def render(self, name, value, attrs=None, renderer=None):
45
46         context = self.get_context(attrs)
47         return mark_safe(render_to_string(self.template_name, context))
48
49     def get_context(self, attrs):
50         return {
51             'items': self.items,
52             'name': self.name,
53             'show_from_noentry': self.show_from_noentry,
54             'show_x_results': self.show_x_results,
55             'results_scrollable': self.results_scrollable,
56             'selectable_limit': self.selectable_limit,
57             'placeholder': self.placeholder,
58             'initial': self.initial,
59         }
60
61
62 class SearchableSelectMultipleField(forms.Field):
63     def __init__(self, *args, required=True, widget=None, label=None, disabled=False,
64                  items=None, queryset=None, show_from_noentry=True, show_x_results=-1,
65                  results_scrollable=False, selectable_limit=-1, placeholder="search here",
66                  name="searchable_select", initial=[], **kwargs):
67         """from the documentation:
68         # required -- Boolean that specifies whether the field is required.
69         #             True by default.
70         # widget -- A Widget class, or instance of a Widget class, that should
71         #           be used for this Field when displaying it. Each Field has a
72         #           default Widget that it'll use if you don't specify this. In
73         #           most cases, the default widget is TextInput.
74         # label -- A verbose name for this field, for use in displaying this
75         #          field in a form. By default, Django will use a "pretty"
76         #          version of the form field name, if the Field is part of a
77         #          Form.
78         # initial -- A value to use in this Field's initial display. This value
79         #            is *not* used as a fallback if data isn't given.
80         # help_text -- An optional string to use as "help text" for this Field.
81         # error_messages -- An optional dictionary to override the default
82         #                   messages that the field will raise.
83         # show_hidden_initial -- Boolean that specifies if it is needed to render a
84         #                        hidden widget with initial value after widget.
85         # validators -- List of additional validators to use
86         # localize -- Boolean that specifies if the field should be localized.
87         # disabled -- Boolean that specifies whether the field is disabled, that
88         #             is its widget is shown in the form but not editable.
89         # label_suffix -- Suffix to be added to the label. Overrides
90         #                 form's label_suffix.
91         """
92
93         self.widget = widget
94         if self.widget is None:
95             self.widget = SearchableSelectMultipleWidget(
96                 attrs={
97                     'items': items,
98                     'initial': [obj.id for obj in initial],
99                     'show_from_noentry': show_from_noentry,
100                     'show_x_results': show_x_results,
101                     'results_scrollable': results_scrollable,
102                     'selectable_limit': selectable_limit,
103                     'placeholder': placeholder,
104                     'name': name,
105                     'disabled': disabled
106                 }
107             )
108         self.disabled = disabled
109         self.queryset = queryset
110         self.selectable_limit = selectable_limit
111
112         super().__init__(disabled=disabled, **kwargs)
113
114         self.required = required
115
116     def clean(self, data):
117         data = data[0]
118         if not data:
119             if self.required:
120                 raise ValidationError("Nothing was selected")
121             else:
122                 return []
123         data_as_list = json.loads(data)
124         if self.selectable_limit != -1:
125             if len(data_as_list) > self.selectable_limit:
126                 raise ValidationError("Too many items were selected")
127
128         items = []
129         for elem in data_as_list:
130             items.append(self.queryset.get(id=elem))
131
132         return items
133
134
135 class SearchableSelectAbstractForm(forms.Form):
136     def __init__(self, *args, queryset=None, initial=[], **kwargs):
137         self.queryset = queryset
138         items = self.generate_items(self.queryset)
139         options = self.generate_options()
140
141         super(SearchableSelectAbstractForm, self).__init__(*args, **kwargs)
142         self.fields['searchable_select'] = SearchableSelectMultipleField(
143             initial=initial,
144             items=items,
145             queryset=self.queryset,
146             **options
147         )
148
149     def get_validated_bundle(self):
150         bundles = self.cleaned_data['searchable_select']
151         if len(bundles) < 1:  # don't need to check for >1, as field does that for us
152             raise ValidationError("No bundle was selected")
153         return bundles[0]
154
155     def generate_items(self, queryset):
156         raise Exception("SearchableSelectAbstractForm does not implement concrete generate_items()")
157
158     def generate_options(self, disabled=False):
159         return {
160             'show_from_noentry': True,
161             'show_x_results': -1,
162             'results_scrollable': True,
163             'selectable_limit': 1,
164             'placeholder': 'Search for a Bundle',
165             'name': 'searchable_select',
166             'disabled': False
167         }
168
169
170 class SWConfigSelectorForm(SearchableSelectAbstractForm):
171     def generate_items(self, queryset):
172         items = {}
173
174         for bundle in queryset:
175             items[bundle.id] = {
176                 'expanded_name': bundle.name,
177                 'small_name': bundle.owner.username,
178                 'string': bundle.description,
179                 'id': bundle.id
180             }
181
182         return items
183
184
185 class OPNFVSelectForm(SearchableSelectAbstractForm):
186     def generate_items(self, queryset):
187         items = {}
188
189         for config in queryset:
190             items[config.id] = {
191                 'expanded_name': config.name,
192                 'small_name': config.bundle.owner.username,
193                 'string': config.description,
194                 'id': config.id
195             }
196
197         return items
198
199
200 class ResourceSelectorForm(SearchableSelectAbstractForm):
201     def generate_items(self, queryset):
202         items = {}
203
204         for bundle in queryset:
205             items[bundle.id] = {
206                 'expanded_name': bundle.name,
207                 'small_name': bundle.owner.username,
208                 'string': bundle.description,
209                 'id': bundle.id
210             }
211
212         return items
213
214
215 class BookingMetaForm(forms.Form):
216
217     length = forms.IntegerField(
218         widget=NumberInput(
219             attrs={
220                 "type": "range",
221                 'min': "1",
222                 "max": "21",
223                 "value": "1"
224             }
225         )
226     )
227     purpose = forms.CharField(max_length=1000)
228     project = forms.CharField(max_length=400)
229     info_file = forms.CharField(max_length=1000, required=False)
230     deploy_opnfv = forms.BooleanField(required=False)
231
232     def __init__(self, *args, user_initial=[], owner=None, **kwargs):
233         super(BookingMetaForm, self).__init__(**kwargs)
234
235         self.fields['users'] = SearchableSelectMultipleField(
236             queryset=UserProfile.objects.select_related('user').exclude(user=owner),
237             initial=user_initial,
238             items=get_user_items(exclude=owner),
239             required=False,
240             **get_user_field_opts()
241         )
242
243
244 class MultipleSelectFilterWidget(forms.Widget):
245     def __init__(self, attrs=None):
246         super(MultipleSelectFilterWidget, self).__init__(attrs)
247         self.attrs = attrs
248         self.template_name = "dashboard/multiple_select_filter_widget.html"
249
250     def render(self, name, value, attrs=None, renderer=None):
251         attrs = self.attrs
252         self.context = self.get_context(name, value, attrs)
253         html = render_to_string(self.template_name, context=self.context)
254         return mark_safe(html)
255
256     def get_context(self, name, value, attrs):
257         return attrs
258
259
260 class MultipleSelectFilterField(forms.Field):
261
262     def __init__(self, required=True, widget=None, label=None, initial=None,
263                  help_text='', error_messages=None, show_hidden_initial=False,
264                  validators=(), localize=False, disabled=False, label_suffix=None):
265         """from the documentation:
266         # required -- Boolean that specifies whether the field is required.
267         #             True by default.
268         # widget -- A Widget class, or instance of a Widget class, that should
269         #           be used for this Field when displaying it. Each Field has a
270         #           default Widget that it'll use if you don't specify this. In
271         #           most cases, the default widget is TextInput.
272         # label -- A verbose name for this field, for use in displaying this
273         #          field in a form. By default, Django will use a "pretty"
274         #          version of the form field name, if the Field is part of a
275         #          Form.
276         # initial -- A value to use in this Field's initial display. This value
277         #            is *not* used as a fallback if data isn't given.
278         # help_text -- An optional string to use as "help; text" for this Field.
279         # error_messages -- An optional dictionary to override the default
280         #                   messages that the field will raise.
281         # show_hidden_initial -- Boolean that specifies if it is needed to render a
282         #                        hidden widget with initial value after widget.
283         # validators -- List of additional validators to use
284         # localize -- Boolean that specifies if the field should be localized.
285         # disabled -- Boolean that specifies whether the field is disabled, that
286         #             is its widget is shown in the form but not editable.
287         # label_suffix -- Suffix to be added to the label. Overrides
288         #                 form's label_suffix.
289         """
290         # this is bad, but django forms are annoying
291         self.widget = widget
292         if self.widget is None:
293             self.widget = MultipleSelectFilterWidget()
294         super(MultipleSelectFilterField, self).__init__(
295             required=required,
296             widget=self.widget,
297             label=label,
298             initial=None,
299             help_text=help_text,
300             error_messages=error_messages,
301             show_hidden_initial=show_hidden_initial,
302             validators=validators,
303             localize=localize,
304             disabled=disabled,
305             label_suffix=label_suffix
306         )
307
308         def clean(data):
309             """
310             This method will raise a django.forms.ValidationError or return clean data
311             """
312             return data
313
314
315 class FormUtils:
316     @staticmethod
317     def getLabData(multiple_selectable_hosts):
318         """
319         Gets all labs and thier host profiles and returns a serialized version the form can understand.
320         Should be rewritten with a related query to make it faster
321         Should be moved outside of global scope
322         """
323         labs = {}
324         hosts = {}
325         items = {}
326         mapping = {}
327         for lab in Lab.objects.all():
328             slab = {}
329             slab['id'] = "lab_" + str(lab.lab_user.id)
330             slab['name'] = lab.name
331             slab['description'] = lab.description
332             slab['selected'] = 0
333             slab['selectable'] = 1
334             slab['follow'] = 1
335             if not multiple_selectable_hosts:
336                 slab['follow'] = 0
337             slab['multiple'] = 0
338             items[slab['id']] = slab
339             mapping[slab['id']] = []
340             labs[slab['id']] = slab
341             for host in lab.hostprofiles.all():
342                 shost = {}
343                 shost['forms'] = [{"name": "host_name", "type": "text", "placeholder": "hostname"}]
344                 shost['id'] = "host_" + str(host.id)
345                 shost['name'] = host.name
346                 shost['description'] = host.description
347                 shost['selected'] = 0
348                 shost['selectable'] = 1
349                 shost['follow'] = 0
350                 shost['multiple'] = multiple_selectable_hosts
351                 items[shost['id']] = shost
352                 mapping[slab['id']].append(shost['id'])
353                 if shost['id'] not in mapping:
354                     mapping[shost['id']] = []
355                 mapping[shost['id']].append(slab['id'])
356                 hosts[shost['id']] = shost
357
358         filter_objects = [("labs", labs.values()), ("hosts", hosts.values())]
359
360         context = {
361             'filter_objects': filter_objects,
362             'mapping': mapping,
363             'filter_items': items
364         }
365         return context
366
367
368 class HardwareDefinitionForm(forms.Form):
369
370     def __init__(self, *args, **kwargs):
371         selection_data = kwargs.pop("selection_data", False)
372         super(HardwareDefinitionForm, self).__init__(*args, **kwargs)
373         attrs = FormUtils.getLabData(1)
374         attrs['selection_data'] = selection_data
375         self.fields['filter_field'] = MultipleSelectFilterField(
376             widget=MultipleSelectFilterWidget(
377                 attrs=attrs
378             )
379         )
380
381
382 class PodDefinitionForm(forms.Form):
383
384     fields = ["xml"]
385     xml = forms.CharField()
386
387
388 class ResourceMetaForm(forms.Form):
389
390     bundle_name = forms.CharField(label="POD Name")
391     bundle_description = forms.CharField(label="POD Description", widget=forms.Textarea)
392
393
394 class GenericHostMetaForm(forms.Form):
395
396     host_profile = forms.CharField(label="Host Type", disabled=True, required=False)
397     host_name = forms.CharField(label="Host Name")
398
399
400 class NetworkDefinitionForm(forms.Form):
401     def __init__(self, *args, **kwargs):
402         super(NetworkDefinitionForm, self).__init__(**kwargs)
403
404
405 class NetworkConfigurationForm(forms.Form):
406     def __init__(self, *args, **kwargs):
407         super(NetworkConfigurationForm).__init__(**kwargs)
408
409
410 class HostSoftwareDefinitionForm(forms.Form):
411
412     host_name = forms.CharField(max_length=200, disabled=True, required=False)
413     headnode = forms.BooleanField(required=False, widget=forms.HiddenInput)
414
415     def __init__(self, *args, **kwargs):
416         imageQS = kwargs.pop("imageQS")
417         super(HostSoftwareDefinitionForm, self).__init__(*args, **kwargs)
418         self.fields['image'] = forms.ModelChoiceField(queryset=imageQS)
419
420
421 class WorkflowSelectionForm(forms.Form):
422     fields = ['workflow']
423
424     empty_permitted = False
425
426     workflow = forms.ChoiceField(
427         choices=(
428             (0, 'Booking'),
429             (1, 'Resource Bundle'),
430             (2, 'Software Configuration')
431         ),
432         label="Choose Workflow",
433         initial='booking',
434         required=True
435     )
436
437
438 class SnapshotHostSelectForm(forms.Form):
439     host = forms.CharField()
440
441
442 class BasicMetaForm(forms.Form):
443     name = forms.CharField()
444     description = forms.CharField(widget=forms.Textarea)
445
446
447 class ConfirmationForm(forms.Form):
448     fields = ['confirm']
449
450     confirm = forms.ChoiceField(
451         choices=(
452             (True, "Confirm"),
453             (False, "Cancel")
454         )
455     )
456
457
458 class OPNFVSelectionForm(forms.Form):
459     installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=True)
460     scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=True)
461
462
463 class OPNFVNetworkRoleForm(forms.Form):
464     role = forms.CharField(max_length=200, disabled=True, required=False)
465
466     def __init__(self, *args, config_bundle, **kwargs):
467         super(OPNFVNetworkRoleForm, self).__init__(*args, **kwargs)
468         self.fields['network'] = forms.ModelChoiceField(
469             queryset=config_bundle.bundle.networks.all()
470         )
471
472
473 class OPNFVHostRoleForm(forms.Form):
474     host_name = forms.CharField(max_length=200, disabled=True, required=False)
475     role = forms.ModelChoiceField(queryset=OPNFVRole.objects.all().order_by("name").distinct("name"))