From: Parker Berberian Date: Mon, 24 Jun 2019 18:02:21 +0000 (+0000) Subject: Merge "Prefetches Collaborators" X-Git-Tag: 2.0.99~88 X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=commitdiff_plain;h=d98ddb1cdf6d9dcd0ad79d12dd12d6c23a6ea2ba;hp=e621a5182f387ab2b666e4c65e655b56139998d3;p=laas.git Merge "Prefetches Collaborators" --- diff --git a/requirements.txt b/requirements.txt index 9ea10a4..55e5fc9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ celery==3.1.23 cryptography==2.3.1 Django==2.1 -django-bootstrap3==10.0.1 +django-bootstrap4==0.0.8 django-crispy-forms==1.7.2 django-filter==2.0.0 django-registration==2.1.2 diff --git a/src/booking/forms.py b/src/booking/forms.py index e48b293..df88cc6 100644 --- a/src/booking/forms.py +++ b/src/booking/forms.py @@ -48,8 +48,7 @@ class QuickBookingForm(forms.Form): ) attrs = FormUtils.getLabData(0) - attrs['selection_data'] = 'false' - self.fields['filter_field'] = MultipleSelectFilterField(widget=MultipleSelectFilterWidget(attrs=attrs)) + self.fields['filter_field'] = MultipleSelectFilterField(widget=MultipleSelectFilterWidget(**attrs)) self.fields['length'] = forms.IntegerField( widget=NumberInput( attrs={ diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py index ac69c8c..0e0cc5a 100644 --- a/src/booking/quick_deployer.py +++ b/src/booking/quick_deployer.py @@ -96,25 +96,22 @@ class BookingPermissionException(Exception): pass -def parse_host_field(host_field_contents): - host_json = json.loads(host_field_contents) - lab_dict = host_json['labs'][0] - lab_id = list(lab_dict.keys())[0] - lab_user_id = int(lab_id.split("_")[-1]) - lab = Lab.objects.get(lab_user__id=lab_user_id) - - host_dict = host_json['hosts'][0] - profile_id = list(host_dict.keys())[0] - profile_id = int(profile_id.split("_")[-1]) - profile = HostProfile.objects.get(id=profile_id) - - # check validity of field data before trying to apply to models - if len(host_json['labs']) != 1: +def parse_host_field(host_json): + lab, profile = (None, None) + lab_dict = host_json['lab'] + for lab_info in lab_dict.values(): + if lab_info['selected']: + lab = Lab.objects.get(lab_user__id=lab_info['id']) + + host_dict = host_json['host'] + for host_info in host_dict.values(): + if host_info['selected']: + profile = HostProfile.objects.get(pk=host_info['id']) + + if lab is None: raise NoLabSelectedError("No lab was selected") - if not lab: - raise LabDNE("Lab with provided ID does not exist") - if not profile: - raise HostProfileDNE("Host type with provided ID does not exist") + if profile is None: + raise HostProfileDNE("No Host was selected") return lab, profile @@ -329,6 +326,8 @@ def create_from_form(form, request): JobFactory.makeCompleteJob(booking) NotificationHandler.notify_new_booking(booking) + return booking + def drop_filter(user): installer_filter = {} diff --git a/src/booking/views.py b/src/booking/views.py index 13e9d01..bad7dc9 100644 --- a/src/booking/views.py +++ b/src/booking/views.py @@ -16,6 +16,7 @@ from django.views import View from django.views.generic import TemplateView from django.shortcuts import redirect, render from django.db.models import Q +from django.urls import reverse from resource_inventory.models import ResourceBundle, HostProfile, Image, Host from resource_inventory.resource_manager import ResourceManager @@ -60,14 +61,13 @@ def quick_create(request): if form.is_valid(): try: - create_from_form(form, request) + booking = create_from_form(form, request) + messages.success(request, "We've processed your request. " + "Check Account->My Bookings for the status of your new booking") + return redirect(reverse('booking:booking_detail', kwargs={'booking_id': booking.id})) except Exception as e: messages.error(request, "Whoops, an error occurred: " + str(e)) - return render(request, 'workflow/exit_redirect.html', context) - - messages.success(request, "We've processed your request. " - "Check Account->My Bookings for the status of your new booking") - return render(request, 'workflow/exit_redirect.html', context) + return render(request, 'booking/quick_deploy.html', context) else: messages.error(request, "Looks like the form didn't validate. Check that you entered everything correctly") return render(request, 'booking/quick_deploy.html', context) diff --git a/src/pharos_dashboard/settings.py b/src/pharos_dashboard/settings.py index 793eec7..86de78c 100644 --- a/src/pharos_dashboard/settings.py +++ b/src/pharos_dashboard/settings.py @@ -35,7 +35,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.humanize', - 'bootstrap3', + 'bootstrap4', 'crispy_forms', 'rest_framework', 'rest_framework.authtoken', diff --git a/src/static/bower.json b/src/static/bower.json index 9ae744a..dda786d 100644 --- a/src/static/bower.json +++ b/src/static/bower.json @@ -16,12 +16,14 @@ "tests" ], "dependencies": { - "eonasdan-bootstrap-datetimepicker": "^4.17.37", "fullcalendar": "^2.9.0", "jquery-migrate": "^3.0.0", - "startbootstrap-sb-admin-2-blackrockdigital": "^3.3.7" - }, - "resolutions": { - "font-awesome": "~4.6.3" + "bootstrap": "4.3.1", + "popper.js": "1.14.3", + "Font-Awesome": "5.9.0", + "datatables.net": "1.10.19", + "datatables.net-bs4": "1.10.19", + "datatables.net-responsive": "2.1.1", + "datatables.net-responsive-bs4": "2.2.3" } } diff --git a/src/static/css/base.css b/src/static/css/base.css new file mode 100644 index 0000000..c51728c --- /dev/null +++ b/src/static/css/base.css @@ -0,0 +1,8 @@ +/* Rotating arrows when dropdown happens */ +i.fas.rotate { + transition: transform 0.3s ease-in-out; +} + +a[aria-expanded="true"] > i.rotate { + transform: rotate(180deg); +} diff --git a/src/templates/account/booking_list.html b/src/templates/account/booking_list.html index ed59b81..98ab5c8 100644 --- a/src/templates/account/booking_list.html +++ b/src/templates/account/booking_list.html @@ -3,9 +3,11 @@

Bookings I Own

{% for booking in bookings %} -
-
+
+

Booking {{booking.id}}

+
+
  • id: {{booking.id}}
  • lab: {{booking.lab}}
  • @@ -15,8 +17,8 @@
  • purpose: {{booking.purpose}}
-
- Details + + +
- - + - -
- {% if title %} -
-
-

{{ title }}

+
+ {% if title %} +
+
+

{{ title }}

+
+
- + {% endif %} +
{% bootstrap_messages %}
+ + {% block content %} + {% endblock content %}
- {% endif %} -
{% bootstrap_messages %}
+ - {% block content %} - {% endblock content %}
- +
- + +
+ {% endblock basecontent %} diff --git a/src/templates/booking/booking_calendar.html b/src/templates/booking/booking_calendar.html index 1b29dc2..ddcb45d 100644 --- a/src/templates/booking/booking_calendar.html +++ b/src/templates/booking/booking_calendar.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% load staticfiles %} -{% load bootstrap3 %} +{% load bootstrap4 %} {% block extrahead %} {{ block.super }} diff --git a/src/templates/booking/booking_delete.html b/src/templates/booking/booking_delete.html index 76a5634..b89eb15 100644 --- a/src/templates/booking/booking_delete.html +++ b/src/templates/booking/booking_delete.html @@ -1,5 +1,5 @@ {% load jira_filters %} -{% load bootstrap3 %} +{% load bootstrap4 %}

Really delete Booking from {{ booking.start}} to {{ booking.end }}? diff --git a/src/templates/booking/booking_detail.html b/src/templates/booking/booking_detail.html index ac00e22..918f5af 100644 --- a/src/templates/booking/booking_detail.html +++ b/src/templates/booking/booking_detail.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load staticfiles %} -{% load bootstrap3 %} +{% load bootstrap4 %} {% block extrahead %} {{block.super}} @@ -19,13 +19,13 @@

-
-
-
+
+
+

Overview

- Expand +
-
+
@@ -60,12 +60,12 @@
-
-
+
+

Pod

- Expand +
-
+
Purpose
{% for host in booking.resource.hosts.all %} @@ -177,14 +177,14 @@ -
-
-
+
+
+

Deployment Progress

These are the different tasks that have to be completed before your deployment is ready

- Expand +
-
+
-
-
-

About Us:

-

The Lab as a Service (LaaS) project aims to help in the development and testing of LFN projects such as OPNFV by hosting hardware and providing access to the community. Currently, the only participating lab is the University of New Hampshire Interoperability Lab (UNH-IOL).

-

To get started, you can request access to a server at the right. PTL's have the ability to design and book a whole block of servers with customized layer2 networks (e.g. a Pharos Pod). Read more here: LaaS Wiki

+ h1 {} + +
+
+ +
+

About Us:

+

The Lab as a Service (LaaS) project aims to help in the development and testing of LFN projects such as + OPNFV + by hosting hardware and providing access to the community. Currently, the only participating lab is the + University of New Hampshire Interoperability Lab (UNH-IOL).

+

To get started, you can request access to a server at the right. PTL's have the ability to design and + book a + whole block of servers with customized layer2 networks (e.g. a Pharos Pod). Read more here: LaaS Wiki

+
+ +
+

Get Started:

+ {% if request.user.is_anonymous %} +

To get started, please log in with your Linux + Foundation Jira account

+ {% else %} +

To get started, book a server below:

+ +

Book a Server

+
+

PTLs can use our advanced options to book multi-node pods. If you are a PTL, you may use the options + below: +

+
+
+
+ +
+
+ +
+
+ +
+
+ {% endif %} +
+
+ {% if not request.user.is_anonymous %} -

Returning Users:

-

If you're a returning user, some of the following options may be of interest:

- - My Bookings - {% if manager == True %} - - {% endif %} - {% endif %} -
-
-
-
-

Get Started:

- {% if request.user.is_anonymous %} -

To get started, please log in with your Linux Foundation Jira account

- {% else %} -

To get started, book a server below:

-

Book a Server

-

PTLs can use our advanced options to book multi-node pods. If you are a PTL, you may use the options below:

-
- - - - - {% endif %} +
+

Returning Users:

+

If you're a returning user, some of the following options may be of interest:

+
+
+
+ +
+ + {% if manager == True %} +
+ +
+ {% endif %} +
+
+ {% endif %}
- - - -
@@ -130,4 +169,4 @@ {% block vport_comm %} {% endblock %} -{% endblock content %} +{% endblock content %} \ No newline at end of file diff --git a/src/templates/dashboard/multiple_select_filter_widget.html b/src/templates/dashboard/multiple_select_filter_widget.html index 536fdcc..3a7e148 100644 --- a/src/templates/dashboard/multiple_select_filter_widget.html +++ b/src/templates/dashboard/multiple_select_filter_widget.html @@ -4,6 +4,7 @@ grid-template-columns: 1fr 1fr 1fr; border: 0px; } + .class_grid_wrapper { border: 0px; text-align: center; @@ -20,96 +21,78 @@ display: grid; grid-template-columns: 1fr 1fr; } + .grid-item { cursor: pointer; - border:1px solid #cccccc; + border: 1px solid #cccccc; border-radius: 5px; - margin:20px; + margin: 20px; height: 200px; padding: 7px; - transition-property: box-shadow, background-color; - transition-duration: .2s; -} - -.grid-item:hover { - box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.45); - transition-property: box-shadow; - transition-duration: .2s; - + transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s; + box-shadow: 0 1px 1px rgba(0,0,0,.075); } .selected_node { - box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.45); - background-color: #fff; - transition-property: background-color; - transition-duration: .2s; + border-color: #40c640; + box-shadow: 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(109, 243, 76, 0.6); + transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s; } .disabled_node { cursor: not-allowed; background-color: #EFEFEF; - transition-property: box-shadow; - transition-duration: .2s; - border: 1px solid #ccc; } -.disabled_node:hover { -} +.disabled_node:hover {} .cleared_node { background-color: #FFFFFF; } -.grid-item-header -{ +.grid-item-header { font-weight: bold; font-size: 20px; margin-top: 10px; } -#dropdown_wrapper > div > h5 { - margin: 12px; - display: inline-block; - vertical-align: middle; +.dropdown_item { + border: 1px; + border-style: solid; + border-color: lightgray; + border-radius: 5px; + margin: 20px; + padding: 2px; + grid-column: 1; + display: grid; + grid-template-columns: 1fr 3fr 1fr; + justify-items: center; } -#dropdown_wrapper > div > button { - padding: 7px; +.dropdown_item > button { margin: 2px; - float: right; - width: 80px; + justify-self: end; } -#dropdown_wrapper > div > input { - padding: 7px; - margin: 2px; - float: right; - width: 300px; - width: calc(100% - 240px); + +.dropdown_item > h5 { + margin: auto; } -#dropdown_wrapper > div { - border:2px; - border-style:none; - border-color:black; - border-radius: 5px; - margin:20px; - padding: 2px; - box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75); - transition-property: box-shadow, background-color; - transition-duration: .2s; - display: inline-block; - vertical-align: middle; +.dropdown_item > input { + padding: 7px; + margin: 2px; + width: 90%; } #dropdown_wrapper { display: grid; - grid-template-columns: 3fr 5fr; + grid-template-columns: 4fr 5fr; } - +
-{% for object_class, object_list in filter_objects %} +{% for object_class, object_list in display_objects %}

{{object_class}}

@@ -120,12 +103,8 @@

{{obj.name}}

{{obj.description}}

+ '{{obj.id}}');">{% if obj.multiple %}Add{% else %}Select{% endif %}
- {% endfor %}
@@ -137,62 +116,55 @@ - + + diff --git a/src/templates/dashboard/resource_all.html b/src/templates/dashboard/resource_all.html index 0b0d0d4..fb8cc7e 100644 --- a/src/templates/dashboard/resource_all.html +++ b/src/templates/dashboard/resource_all.html @@ -7,11 +7,11 @@ - - {% endblock extrahead %} @@ -36,11 +36,11 @@ {% block extrajs %} - - - + + diff --git a/src/templates/dashboard/table.html b/src/templates/dashboard/table.html index b3f4b5f..0a37ded 100644 --- a/src/templates/dashboard/table.html +++ b/src/templates/dashboard/table.html @@ -4,11 +4,12 @@ {% block extrahead %} {{ block.super }} - - + {% endblock extrahead %} {% block content %} @@ -34,8 +35,8 @@ {% block extrajs %} - - + + diff --git a/src/templates/layout.html b/src/templates/layout.html index 378cc63..d37d4f5 100644 --- a/src/templates/layout.html +++ b/src/templates/layout.html @@ -20,7 +20,7 @@ - @@ -50,17 +50,12 @@ {##} {##} {##} - + + {##} - - - - - - {% block extrajs %} {% endblock extrajs %} diff --git a/src/templates/notifier/inbox.html b/src/templates/notifier/inbox.html index 4184d1d..72207ed 100644 --- a/src/templates/notifier/inbox.html +++ b/src/templates/notifier/inbox.html @@ -6,97 +6,124 @@ {% block content %} - -
-
-

New:

-
- {% for notification in unread_notifications %} -
- {{ notification }} +
+
+
+
+ + + +
- {% endfor %}
-

Read:

-
- {% for notification in read_notifications %} -
- {{ notification }} +
+ +
+
+ {% for notification in unread_notifications %} + + {{ notification }} + + {% endfor %} +
+
+ {% for notification in read_notifications %} + + {{ notification }} + + {% endfor %} +
+
+ +
+
- {% endfor %}
-
-
-
-
-
- -
-
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/src/templates/resource/hostprofile_detail.html b/src/templates/resource/hostprofile_detail.html index 0776b9e..dc20600 100644 --- a/src/templates/resource/hostprofile_detail.html +++ b/src/templates/resource/hostprofile_detail.html @@ -4,45 +4,35 @@ {% block content %}
-
-
+
+

Available at

- Expand +
-
-
- - - -
-
    - {% for lab in hostprofile.labs.all %} -
  • {{lab.name}}
  • - {% endfor %} -
-
+
+
    + {% for lab in hostprofile.labs.all %} +
  • {{lab.name}}
  • + {% endfor %} +
-
-
+
+

RAM

- Expand +
-
- - - - -
{{hostprofile.ramprofile.first.amount}}G, - {{hostprofile.ramprofile.first.channels}} channels
+
+ {{hostprofile.ramprofile.first.amount}}G, + {{hostprofile.ramprofile.first.channels}} channels
-
-
+
+

CPU

- Expand +
-
+
@@ -59,42 +49,12 @@
Arch:
-
-
-
-
-

Interfaces

- Expand -
-
- - {% for intprof in hostprofile.interfaceprofile.all %} - - - - {% endfor %} -
- - - - - - - - - -
Name:{{intprof.name}}
Speed:{{intprof.speed}}
-
-
-
-
-
-
-
+
+

Disk

- Expand +
-
+
@@ -112,5 +72,31 @@ +
+
+
+

Interfaces

+ +
+
+
Size:
+ + + + + + + + {% for intprof in hostprofile.interfaceprofile.all %} + + + + + {% endfor %} + +
NameSpeed
{{intprof.name}}{{intprof.speed}}
+
+
+
{% endblock content %} diff --git a/src/templates/resource/steps/define_hardware.html b/src/templates/resource/steps/define_hardware.html index 933b4ab..77df5a2 100644 --- a/src/templates/resource/steps/define_hardware.html +++ b/src/templates/resource/steps/define_hardware.html @@ -1,7 +1,7 @@ {% extends "workflow/viewport-element.html" %} {% load staticfiles %} -{% load bootstrap3 %} +{% load bootstrap4 %} {% block content %}

Note that not all labs host every kind of machine. @@ -26,7 +26,7 @@ var normalize = function(data){ } return normalized; } -var data = normalize(result); +var data = result; data = JSON.stringify(data); document.getElementById("filter_field").value = data; var formData = $("#define_hardware_form").serialize(); diff --git a/src/templates/resource/steps/host_info.html b/src/templates/resource/steps/host_info.html index 0275727..bbbafdc 100644 --- a/src/templates/resource/steps/host_info.html +++ b/src/templates/resource/steps/host_info.html @@ -1,7 +1,7 @@ {% extends "workflow/viewport-element.html" %} {% load staticfiles %} -{% load bootstrap3 %} +{% load bootstrap4 %} {% block content %} diff --git a/src/templates/resource/steps/meta_info.html b/src/templates/resource/steps/meta_info.html index da98267..cebd343 100644 --- a/src/templates/resource/steps/meta_info.html +++ b/src/templates/resource/steps/meta_info.html @@ -1,7 +1,7 @@ {% extends "workflow/viewport-element.html" %} {% load staticfiles %} -{% load bootstrap3 %} +{% load bootstrap4 %} {% block content %} diff --git a/src/templates/snapshot_workflow/steps/meta.html b/src/templates/snapshot_workflow/steps/meta.html index cc49691..bea475d 100644 --- a/src/templates/snapshot_workflow/steps/meta.html +++ b/src/templates/snapshot_workflow/steps/meta.html @@ -1,7 +1,7 @@ {% extends "workflow/viewport-element.html" %} {% load staticfiles %} -{% load bootstrap3 %} +{% load bootstrap4 %} {% block content %} - - + #topPagination .topcrumb { + flex: 1 1 0; + display: flex; + align-content: center; + justify-content: center; + border: 1px solid #dee2e6; + border-left: none; + } + + .topcrumb > span { + color: #343a40; + cursor: default; + } -

- + .topcrumb.active > span { + background: #007bff; + color: white; + } + + .topcrumb.disabled > span { + color: #6c757d; + background: #f8f9fa; + } + + + + +
+
+
+
+

+ +

+
+ + +
+
+
+ +
+
+ +
+
+
+ +
+ + +
+
+
-
{% csrf_token %} -
-
-

-

-

-
- - - -
- -
- +
+ {% csrf_token %} +
-{% endblock content %} +{% endblock content %} \ No newline at end of file diff --git a/src/templates/workflow/viewport-element.html b/src/templates/workflow/viewport-element.html index f25e644..7a7165a 100644 --- a/src/templates/workflow/viewport-element.html +++ b/src/templates/workflow/viewport-element.html @@ -1,5 +1,5 @@ {% extends "layout.html" %} -{% load bootstrap3 %} +{% load bootstrap4 %} {% load staticfiles %} {% block basecontent %} 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()