From 46ab5d82161a4a6ee368dc2adf6e0ca0dea38799 Mon Sep 17 00:00:00 2001 From: Parker Berberian Date: Tue, 18 Jun 2019 14:58:27 -0400 Subject: Redesigns Multiple Select Filter Widget Makes the filter widget work as it should so that it can be integrated with the rest of the Django form handling nicely. Also fixes a lot of ugly code tangential to the widget. Change-Id: Ib92db8e584f3d2162c6c43a18b75a57273bb18f5 Signed-off-by: Parker Berberian --- src/workflow/forms.py | 169 +++++++++++++------------------ src/workflow/resource_bundle_workflow.py | 80 ++++++--------- 2 files changed, 100 insertions(+), 149 deletions(-) (limited to 'src/workflow') diff --git a/src/workflow/forms.py b/src/workflow/forms.py index 0fb45d6..ee44ecd 100644 --- a/src/workflow/forms.py +++ b/src/workflow/forms.py @@ -242,124 +242,101 @@ class BookingMetaForm(forms.Form): class MultipleSelectFilterWidget(forms.Widget): - def __init__(self, attrs=None): - super(MultipleSelectFilterWidget, self).__init__(attrs) - self.attrs = attrs + def __init__(self, *args, display_objects=None, filter_items=None, neighbors=None, **kwargs): + super(MultipleSelectFilterWidget, self).__init__(*args, **kwargs) + self.display_objects = display_objects + self.filter_items = filter_items + self.neighbors = neighbors self.template_name = "dashboard/multiple_select_filter_widget.html" def render(self, name, value, attrs=None, renderer=None): - attrs = self.attrs - self.context = self.get_context(name, value, attrs) - html = render_to_string(self.template_name, context=self.context) + context = self.get_context(name, value, attrs) + html = render_to_string(self.template_name, context=context) return mark_safe(html) def get_context(self, name, value, attrs): - return attrs + return { + 'display_objects': self.display_objects, + 'neighbors': self.neighbors, + 'filter_items': self.filter_items, + 'initial_value': value + } class MultipleSelectFilterField(forms.Field): - def __init__(self, required=True, widget=None, label=None, initial=None, - help_text='', error_messages=None, show_hidden_initial=False, - validators=(), localize=False, disabled=False, label_suffix=None): - """from the documentation: - # required -- Boolean that specifies whether the field is required. - # True by default. - # widget -- A Widget class, or instance of a Widget class, that should - # be used for this Field when displaying it. Each Field has a - # default Widget that it'll use if you don't specify this. In - # most cases, the default widget is TextInput. - # label -- A verbose name for this field, for use in displaying this - # field in a form. By default, Django will use a "pretty" - # version of the form field name, if the Field is part of a - # Form. - # initial -- A value to use in this Field's initial display. This value - # is *not* used as a fallback if data isn't given. - # help_text -- An optional string to use as "help; text" for this Field. - # error_messages -- An optional dictionary to override the default - # messages that the field will raise. - # show_hidden_initial -- Boolean that specifies if it is needed to render a - # hidden widget with initial value after widget. - # validators -- List of additional validators to use - # localize -- Boolean that specifies if the field should be localized. - # disabled -- Boolean that specifies whether the field is disabled, that - # is its widget is shown in the form but not editable. - # label_suffix -- Suffix to be added to the label. Overrides - # form's label_suffix. - """ - # this is bad, but django forms are annoying - self.widget = widget - if self.widget is None: - self.widget = MultipleSelectFilterWidget() - super(MultipleSelectFilterField, self).__init__( - required=required, - widget=self.widget, - label=label, - initial=None, - help_text=help_text, - error_messages=error_messages, - show_hidden_initial=show_hidden_initial, - validators=validators, - localize=localize, - disabled=disabled, - label_suffix=label_suffix - ) + def __init__(self, **kwargs): + self.initial = kwargs.get("initial") + super().__init__(**kwargs) - def clean(data): - """ - This method will raise a django.forms.ValidationError or return clean data - """ - return data + def to_python(self, value): + return json.loads(value) class FormUtils: @staticmethod - def getLabData(multiple_selectable_hosts): + def getLabData(multiple_hosts=False): """ Gets all labs and thier host profiles and returns a serialized version the form can understand. Should be rewritten with a related query to make it faster - Should be moved outside of global scope """ + # javascript truthy variables + true = 1 + false = 0 + if multiple_hosts: + multiple_hosts = true + else: + multiple_hosts = false labs = {} hosts = {} items = {} - mapping = {} + neighbors = {} for lab in Lab.objects.all(): - slab = {} - slab['id'] = "lab_" + str(lab.lab_user.id) - slab['name'] = lab.name - slab['description'] = lab.description - slab['selected'] = 0 - slab['selectable'] = 1 - slab['follow'] = 1 - if not multiple_selectable_hosts: - slab['follow'] = 0 - slab['multiple'] = 0 - items[slab['id']] = slab - mapping[slab['id']] = [] - labs[slab['id']] = slab + lab_node = { + 'id': "lab_" + str(lab.lab_user.id), + 'model_id': lab.lab_user.id, + 'name': lab.name, + 'description': lab.description, + 'selected': false, + 'selectable': true, + 'follow': false, + 'multiple': false, + 'class': 'lab' + } + if multiple_hosts: + # "follow" this lab node to discover more hosts if allowed + lab_node['follow'] = true + items[lab_node['id']] = lab_node + neighbors[lab_node['id']] = [] + labs[lab_node['id']] = lab_node + for host in lab.hostprofiles.all(): - shost = {} - shost['forms'] = [{"name": "host_name", "type": "text", "placeholder": "hostname"}] - shost['id'] = "host_" + str(host.id) - shost['name'] = host.name - shost['description'] = host.description - shost['selected'] = 0 - shost['selectable'] = 1 - shost['follow'] = 0 - shost['multiple'] = multiple_selectable_hosts - items[shost['id']] = shost - mapping[slab['id']].append(shost['id']) - if shost['id'] not in mapping: - mapping[shost['id']] = [] - mapping[shost['id']].append(slab['id']) - hosts[shost['id']] = shost - - filter_objects = [("labs", labs.values()), ("hosts", hosts.values())] + host_node = { + 'form': {"name": "host_name", "type": "text", "placeholder": "hostname"}, + 'id': "host_" + str(host.id), + 'model_id': host.id, + 'name': host.name, + 'description': host.description, + 'selected': false, + 'selectable': true, + 'follow': false, + 'multiple': multiple_hosts, + 'class': 'host' + } + if multiple_hosts: + host_node['values'] = [] # place to store multiple values + items[host_node['id']] = host_node + neighbors[lab_node['id']].append(host_node['id']) + if host_node['id'] not in neighbors: + neighbors[host_node['id']] = [] + neighbors[host_node['id']].append(lab_node['id']) + hosts[host_node['id']] = host_node + + display_objects = [("lab", labs.values()), ("host", hosts.values())] context = { - 'filter_objects': filter_objects, - 'mapping': mapping, + 'display_objects': display_objects, + 'neighbors': neighbors, 'filter_items': items } return context @@ -368,14 +345,10 @@ class FormUtils: class HardwareDefinitionForm(forms.Form): def __init__(self, *args, **kwargs): - selection_data = kwargs.pop("selection_data", False) super(HardwareDefinitionForm, self).__init__(*args, **kwargs) - attrs = FormUtils.getLabData(1) - attrs['selection_data'] = selection_data + attrs = FormUtils.getLabData(multiple_hosts=True) self.fields['filter_field'] = MultipleSelectFilterField( - widget=MultipleSelectFilterWidget( - attrs=attrs - ) + widget=MultipleSelectFilterWidget(**attrs) ) diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py index ced355f..a4657ab 100644 --- a/src/workflow/resource_bundle_workflow.py +++ b/src/workflow/resource_bundle_workflow.py @@ -52,65 +52,47 @@ class Define_Hardware(WorkflowStep): description = "Choose the type and amount of machines you want" short_title = "hosts" + def __init__(self, *args, **kwargs): + self.form = None + super().__init__(*args, **kwargs) + def get_context(self): context = super(Define_Hardware, self).get_context() - selection_data = {"hosts": {}, "labs": {}} - models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}) - hosts = models.get("hosts", []) - for host in hosts: - profile_id = "host_" + str(host.profile.id) - if profile_id not in selection_data['hosts']: - selection_data['hosts'][profile_id] = [] - selection_data['hosts'][profile_id].append({"host_name": host.resource.name, "class": profile_id}) - - if models.get("bundle", GenericResourceBundle()).lab: - selection_data['labs'] = {"lab_" + str(models.get("bundle").lab.lab_user.id): "true"} - - form = HardwareDefinitionForm( - selection_data=selection_data - ) - context['form'] = form + context['form'] = self.form or HardwareDefinitionForm() return context - def render(self, request): - self.context = self.get_context() - return render(request, self.template, self.context) - def update_models(self, data): - data = json.loads(data['filter_field']) + data = data['filter_field'] models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}) models['hosts'] = [] # This will always clear existing data when this step changes models['interfaces'] = {} if "bundle" not in models: models['bundle'] = GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)) - host_data = data['hosts'] + host_data = data['host'] names = {} - for host_dict in host_data: - id = host_dict['class'] - # bit of formatting - id = int(id.split("_")[-1]) + for host_profile_dict in host_data.values(): + id = host_profile_dict['id'] profile = HostProfile.objects.get(id=id) # instantiate genericHost and store in repo - name = host_dict['host_name'] - if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name): - raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point") - if name in names: - raise NonUniqueHostnameException("All hosts must have unique names") - names[name] = True - genericResource = GenericResource(bundle=models['bundle'], name=name) - genericHost = GenericHost(profile=profile, resource=genericResource) - models['hosts'].append(genericHost) - for interface_profile in profile.interfaceprofile.all(): - genericInterface = GenericInterface(profile=interface_profile, host=genericHost) - if genericHost.resource.name not in models['interfaces']: - models['interfaces'][genericHost.resource.name] = [] - models['interfaces'][genericHost.resource.name].append(genericInterface) + for name in host_profile_dict['values'].values(): + if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name): + raise InvalidHostnameException("Invalid hostname: '" + name + "'") + if name in names: + raise NonUniqueHostnameException("All hosts must have unique names") + names[name] = True + genericResource = GenericResource(bundle=models['bundle'], name=name) + genericHost = GenericHost(profile=profile, resource=genericResource) + models['hosts'].append(genericHost) + for interface_profile in profile.interfaceprofile.all(): + genericInterface = GenericInterface(profile=interface_profile, host=genericHost) + if genericHost.resource.name not in models['interfaces']: + models['interfaces'][genericHost.resource.name] = [] + models['interfaces'][genericHost.resource.name].append(genericInterface) # add selected lab to models - for lab_dict in data['labs']: - if list(lab_dict.values())[0]: # True for lab the user selected - lab_user_id = int(list(lab_dict.keys())[0].split("_")[-1]) - models['bundle'].lab = Lab.objects.get(lab_user__id=lab_user_id) + for lab_dict in data['lab'].values(): + if lab_dict['selected']: + models['bundle'].lab = Lab.objects.get(lab_user__id=lab_dict['id']) break # if somehow we get two 'true' labs, we only use one # return to repo @@ -133,15 +115,11 @@ class Define_Hardware(WorkflowStep): try: self.form = HardwareDefinitionForm(request.POST) if self.form.is_valid(): - if len(json.loads(self.form.cleaned_data['filter_field'])['labs']) != 1: - self.set_invalid("Please select one lab") - else: - self.update_models(self.form.cleaned_data) - self.update_confirmation() - self.set_valid("Step Completed") + self.update_models(self.form.cleaned_data) + self.update_confirmation() + self.set_valid("Step Completed") else: self.set_invalid("Please complete the fields highlighted in red to continue") - pass except Exception as e: self.set_invalid(str(e)) self.context = self.get_context() -- cgit 1.2.3-korg