From c7aa0d764b0b5a6bcd7df967f317bf9fbcde8f73 Mon Sep 17 00:00:00 2001 From: Maciej Tronowski Date: Fri, 27 Feb 2015 17:07:35 +0100 Subject: [PATCH] view to submitting jobs --- plgng/settings.py | 4 +- qcg/fields.py | 11 ++ qcg/forms.py | 115 ++++++++++++++++ qcg/static/qcg/main.css | 36 +++++ qcg/templates/qcg/base.html | 3 + qcg/templates/qcg/job_new.html | 291 ++++++++++++++++++++++++++++++++++++++++ qcg/urls.py | 1 + qcg/views.py | 6 +- 8 files changed, 464 insertions(+), 3 deletions(-) create mode 100644 qcg/fields.py create mode 100644 qcg/templates/qcg/job_new.html diff --git a/plgng/settings.py b/plgng/settings.py index 24f298b..6679cb1 100644 --- a/plgng/settings.py +++ b/plgng/settings.py @@ -132,7 +132,7 @@ SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' # 3-rd party settings BOOTSTRAP3 = { - 'horizontal_label_class': 'col-md-4', - 'horizontal_field_class': 'col-md-6', + 'horizontal_label_class': 'col-sm-3 col-md-4', + 'horizontal_field_class': 'col-sm-9 col-md-6', 'set_placeholder': False, } diff --git a/qcg/fields.py b/qcg/fields.py new file mode 100644 index 0000000..3702aad --- /dev/null +++ b/qcg/fields.py @@ -0,0 +1,11 @@ +from django.forms import ChoiceField, MultipleChoiceField + + +class PredefinedChoiceField(ChoiceField): + def valid_value(self, value): + # any value is valid + return True + + +class MultiplePredefinedChoiceField(MultipleChoiceField, PredefinedChoiceField): + pass diff --git a/qcg/forms.py b/qcg/forms.py index 8906e2d..1771327 100644 --- a/qcg/forms.py +++ b/qcg/forms.py @@ -4,10 +4,15 @@ from django.core.validators import RegexValidator from django.template.defaultfilters import capfirst from pyqcg.utils import TaskStatus +from qcg.fields import PredefinedChoiceField, MultiplePredefinedChoiceField from qcg.models import Task, Allocation date_range_validator = RegexValidator(r'[0-9]{2}\.[0-9]{2}\.[0-9]{4} - [0-9]{2}\.[0-9]{2}\.[0-9]{4}') +nodes_validator = RegexValidator(r'^[0-9]{1,3}(:[0-9]{1,2}){0,2}$') +env_name_validator = RegexValidator(r'^[a-zA-Z_][a-zA-Z0-9_]*$') + +CHOICES_PLACEHOLDER = (None, '') class FiltersForm(forms.Form): @@ -54,3 +59,113 @@ class FiltersForm(forms.Form): self.fields['host'].choices = tuple( (host, capfirst(host.split('.', 1)[0])) for host in Allocation.objects.values_list('host_name', flat=True).order_by('host_name').distinct()) + + +class JobDescriptionForm(forms.Form): + class Host(object): + GALERA = 'galera.task.gda.pl' + HYDRA = 'hydra.icm.edu.pl' + INULA = 'inula.man.poznan.pl' + MOSS = 'moss.man.poznan.pl' + NOVA = 'nova.wcss.wroc.pl' + REEF = 'reef.man.poznan.pl' + ZEUS = 'zeus.cyfronet.pl' + + CHOICES = ( + CHOICES_PLACEHOLDER, + (GALERA, u'Galera'), + (HYDRA, u'Hydra'), + (INULA, u'Inula'), + (MOSS, u'Moss'), + (NOVA, u'Supernova'), + (REEF, u'Reef'), + (ZEUS, u'Zeus'), + ) + APPLICATION_CHOICES = ( + CHOICES_PLACEHOLDER, + ('bash', 'BASH'), + ('python', 'Python'), + ('matlab', 'MATLAB'), + ) + QUEUE_CHOICES = ( + CHOICES_PLACEHOLDER, + ('plgid', 'plgrid'), + ('plgid-long', 'plgrid-long'), + ('plgid-testing', 'plgrid-testing'), + ) + MODULES_CHOICES = ( + ('plgrid/apps/python', 'plgrid/apps/python'), + ('plgrid/apps/matlab', 'plgrid/apps/matlab'), + ) + NOTIFY_CHOICES = ( + (0, u'Brak'), + (1, u'E-mail'), + (2, u'XMPP'), + ) + PROCESS_CHOICES = ( + (0, u'Brak'), + (1, u'Polecenie'), + (2, u'Skrypt'), + ) + + application = forms.ChoiceField(choices=APPLICATION_CHOICES, label=u"Aplikacja", required=False) # TODO choices + executable = forms.CharField(label=u"Polecenie", max_length=500, required=False) # TODO grid ftp + arguments = forms.CharField(label=u"Argumenty", max_length=1000, required=False) + + name = forms.CharField(label=u"Nazwa", max_length=100, required=False) + note = forms.CharField(label=u"Notatka", widget=forms.Textarea(attrs={'rows': 2, 'cols': 40}), required=False) + grant = forms.CharField(label=u"Grant", max_length=100, required=False) + + host = forms.ChoiceField(label=u"Host", choices=Host.CHOICES, required=False) + queue = PredefinedChoiceField(choices=QUEUE_CHOICES, label=u"Kolejka", required=False) + procs = forms.IntegerField(label=u"Liczba procesów", min_value=0, required=False) + nodes = forms.CharField(label=u"Topologia węzłów", max_length=10, validators=[nodes_validator], required=False) + wall_time = forms.IntegerField(label=u"Wall time (s)", min_value=0, required=False) # TODO duration field + memory = forms.IntegerField(label=u"Pamięć (MB)", min_value=0, required=False) + memory_per_slot = forms.IntegerField(label=u"Pamięci per proces (MB)", min_value=0, required=False) + + # TODO grid ftp + input = forms.CharField(label=u"Standardowe wejście", max_length=500, required=False) + output = forms.CharField(label=u"Standardowe wyjście", max_length=500, required=False) + error = forms.CharField(label=u"Standardowe wyjście błędów", max_length=500, required=False) + stage_in = forms.CharField(label=u"Stage in", max_length=500, required=False) + stage_out = forms.CharField(label=u"Stage out", max_length=500, required=False) + + properties = MultiplePredefinedChoiceField(label=u"Właściwości węzłów", required=False) + modules = forms.MultipleChoiceField(label=u"Moduły", choices=MODULES_CHOICES, required=False) # TODO choices + + not_after = forms.DateTimeField(label=u"Nie później niż", required=False) + not_before = forms.DateTimeField(label=u"Nie wcześniej niż", required=False) + deadline = forms.IntegerField(label=u"Deadline (s)", min_value=0, required=False) # TODO duration field + reservation = forms.CharField(label=u"Rezerwacja", max_length=100, required=False) + + monitoring = forms.BooleanField(label=u"Portal QCG-Monitoring", required=False) + notify_type = forms.ChoiceField(label=u"Monitorowanie stanu", choices=NOTIFY_CHOICES, required=False, initial=0, + widget=forms.RadioSelect) + notify_address = forms.EmailField(label=u"Adres", required=False) + watch_output_type = forms.ChoiceField(label=u"Monitorowanie wyjścia", choices=NOTIFY_CHOICES, required=False, + initial=0, widget=forms.RadioSelect) + watch_output_address = forms.EmailField(label=u"Adres", required=False) + watch_output_pattern = forms.CharField(label=u"Wzorzec", max_length=500, required=False) + + preprocess_type = forms.ChoiceField(label=u"Preprocessing", choices=PROCESS_CHOICES, required=False, initial=0, + widget=forms.RadioSelect) + preprocess_cmd = forms.CharField(label=u"Polecenie", max_length=1000, required=False) + preprocess_script = forms.CharField(label=u"Skrypt", max_length=500, required=False) # TODO grid ftp + postprocess_type = forms.ChoiceField(label=u"Postprocessing", choices=PROCESS_CHOICES, required=False, initial=0, + widget=forms.RadioSelect) + postprocess_cmd = forms.CharField(label=u"Polecenie", max_length=1000, required=False) + postprocess_script = forms.CharField(label=u"Skrypt", max_length=500, required=False) # TODO grid ftp + native = MultiplePredefinedChoiceField(label=u"Parametry natywne", required=False) + persistent = forms.BooleanField(label=u"Trwałe", required=False) + use_scratch = forms.BooleanField(label=u"Scratch", required=False) + + +class EnvForm(forms.Form): + name = forms.CharField(label=u"Nazwa", max_length=100, validators=[env_name_validator], + widget=forms.TextInput(attrs={'placeholder': u'Nazwa'})) + value = forms.CharField(label=u"Wartość", max_length=500, + widget=forms.TextInput(attrs={'placeholder': u'Wartość'})) + + +EnvFormSet = forms.formset_factory(EnvForm, can_delete=True, extra=0) diff --git a/qcg/static/qcg/main.css b/qcg/static/qcg/main.css index 57a2b84..4edc075 100644 --- a/qcg/static/qcg/main.css +++ b/qcg/static/qcg/main.css @@ -16,3 +16,39 @@ footer.navbar-fixed-bottom { .page-header { margin-top: 0; } + +textarea { + resize: vertical; +} + + +.modal-body > *:last-child { + margin-bottom: 0; +} + + +#env-controls .form-group { + display: inline-block; + margin: 0; + vertical-align: middle; +} + + +#env-controls > div { + margin-bottom: 7px; +} + + +#env-controls > div:last-of-type { + margin: 0; +} + + +#env-controls input[type="text"] { + width: 160px; +} + + +#add-env-form { + padding-top: 7px; +} diff --git a/qcg/templates/qcg/base.html b/qcg/templates/qcg/base.html index fcf6291..19841b4 100644 --- a/qcg/templates/qcg/base.html +++ b/qcg/templates/qcg/base.html @@ -31,6 +31,9 @@ Zadania + + Zleć zadanie + {% endif %} diff --git a/qcg/templates/qcg/job_new.html b/qcg/templates/qcg/job_new.html new file mode 100644 index 0000000..d1e5de6 --- /dev/null +++ b/qcg/templates/qcg/job_new.html @@ -0,0 +1,291 @@ +{% extends 'qcg/base.html' %} +{% load staticfiles bootstrap3 qcg_utils %} + +{% block extra_css %} + + +{% endblock %} + +{% block extra_js %} + + + + + + + +{% endblock %} + +{% block container %} +

{% block title %}Zleć zadanie{% endblock %}

+ +
+ {% csrf_token %} + + + + +
+
+ {% bootstrap_field form.application layout="horizontal" %} +
+
+ {# TODO grid ftp #} + wybierz plik główny » +
+
+ + {% bootstrap_field form.executable layout="horizontal" form_group_class="form-group collapse" %} + {% bootstrap_field form.arguments layout="horizontal" %} + {% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.note layout="horizontal" %} + {% bootstrap_field form.grant layout="horizontal" %} +
+ +
+ {% bootstrap_field form.host layout="horizontal" %} + {% bootstrap_field form.queue layout="horizontal" %} + {% bootstrap_field form.procs layout="horizontal" %} + {% bootstrap_field form.nodes layout="horizontal" form_group_class="form-group collapse" %} + {% bootstrap_field form.wall_time layout="horizontal" %} + {% bootstrap_field form.memory layout="horizontal" %} + {% bootstrap_field form.memory_per_slot layout="horizontal" %} +
+ +
+ {% bootstrap_field form.input layout="horizontal" %} + {% bootstrap_field form.output layout="horizontal" %} + {% bootstrap_field form.error layout="horizontal" %} + {% bootstrap_field form.stage_in layout="horizontal" %} + {% bootstrap_field form.stage_out layout="horizontal" %} +
+ +
+ {% bootstrap_field form.properties layout="horizontal" %} + {{ env_formset.management_form }} + +
+ +
+
+ {% for env_form in env_formset %} +
{% bootstrap_form env_form layout='inline' %}
+ {% endfor %} +
+
+ +
+
+ + {% bootstrap_field form.modules layout="horizontal" %} +
+ +
+ {% bootstrap_field form.not_before layout="horizontal" %} + {% bootstrap_field form.not_after layout="horizontal" %} + {% bootstrap_field form.deadline layout="horizontal" %} + {% bootstrap_field form.reservation layout="horizontal" form_group_class="form-group collapse" %} +
+ +
+ {% bootstrap_checkbox form.monitoring %} + +
+ +
+
+ {% for option in form.notify_type %} + + {% endfor %} +
+
+
+ +
+
+ +
+ {% bootstrap_field form.notify_address layout="horizontal" %} +
+
+ +
+ +
+
+ {% for option in form.watch_output_type %} + + {% endfor %} +
+
+
+ +
+
+ +
+ {% bootstrap_field form.watch_output_address layout="horizontal" %} + {% bootstrap_field form.watch_output_pattern layout="horizontal" %} +
+
+
+ +
+
+ +
+
+ {% for option in form.preprocess_type %} + + {% endfor %} +
+
+
+ +
+
+ +
+ {% bootstrap_field form.preprocess_cmd layout="horizontal" %} +
+ +
+ {% bootstrap_field form.preprocess_script layout="horizontal" %} +
+
+ +
+ +
+
+ {% for option in form.postprocess_type %} + + {% endfor %} +
+
+
+ +
+
+ +
+ {% bootstrap_field form.postprocess_cmd layout="horizontal" %} +
+ +
+ {% bootstrap_field form.postprocess_script layout="horizontal" %} +
+
+ + {% bootstrap_field form.native layout="horizontal" %} + {% bootstrap_checkbox form.persistent %} + {% bootstrap_checkbox form.use_scratch %} +
+
+ +
+ +
+
+ + + Pokaż zaawansowane +
+
+
+ +{% endblock %} diff --git a/qcg/urls.py b/qcg/urls.py index f265b0f..085bfd3 100644 --- a/qcg/urls.py +++ b/qcg/urls.py @@ -9,6 +9,7 @@ urlpatterns = patterns('', url(r'^$', views.index, name='index'), url(r'^jobs/$', views.jobs_list, name='jobs'), + url(r'^job/new/$', views.job_new, name='job_new'), url(r'^job/(?P[\w]+)/?$', views.job_details, name='job'), url(r'^job/(?P[\w]+)/(?P[\w]+)/?$', views.task_details, name='task'), ) diff --git a/qcg/views.py b/qcg/views.py index e996da0..f0de4dc 100644 --- a/qcg/views.py +++ b/qcg/views.py @@ -13,7 +13,7 @@ from django_openid_auth.views import make_consumer from openid.extensions import ax from pyqcg import QCG -from qcg.forms import FiltersForm +from qcg.forms import FiltersForm, JobDescriptionForm, EnvFormSet from qcg.utils import update_user_data, paginator_context @@ -133,3 +133,7 @@ def task_details(request, job_id, task_id): job__job_id=job_id, task_id=task_id) return render(request, 'qcg/task.html', {'task': task}) + + +def job_new(request): + return render(request, 'qcg/job_new.html', {'form': JobDescriptionForm(), 'env_formset': EnvFormSet()}) -- 1.7.9.5