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