job templates: saving and deleting
authorMaciej Tronowski <mtro@man.poznan.pl>
Thu, 30 Apr 2015 14:04:50 +0000 (16:04 +0200)
committerMaciej Tronowski <mtro@man.poznan.pl>
Thu, 30 Apr 2015 14:04:50 +0000 (16:04 +0200)
qcg/forms.py
qcg/models.py
qcg/service.py
qcg/templates/qcg/base.html
qcg/templates/qcg/job_submit.html
qcg/templates/qcg/job_templates.html
qcg/urls.py
qcg/views.py

index d3d1915..93f9c29 100644 (file)
@@ -5,7 +5,7 @@ from django.template.defaultfilters import capfirst
 from pyqcg.utils import TaskStatus
 
 from qcg.fields import TimeRangeField
-from qcg.models import Task, Allocation
+from qcg.models import Task, Allocation, JobTemplate
 
 
 date_range_validator = RegexValidator(r'[0-9]{2}\.[0-9]{2}\.[0-9]{4} - [0-9]{2}\.[0-9]{2}\.[0-9]{4}')
@@ -253,3 +253,9 @@ class ColumnsForm(forms.Form):
 
     columns = forms.MultipleChoiceField(choices=COLUMNS_CHOICES, initial=[k for k, v in COLUMNS_CHOICES[1:]],
                                         label=u"Kolumny", required=False, widget=forms.CheckboxSelectMultiple)
+
+
+class JobTemplateForm(forms.ModelForm):
+    class Meta:
+        model = JobTemplate
+        fields = ('name',)
index 608c59f..580944c 100644 (file)
@@ -250,3 +250,6 @@ class JobTemplate(models.Model):
 
     def __unicode__(self):
         return u"{} ({})".format(self.name, self.owner)
+
+    def get_absolute_url(self):
+        return reverse('template_submit', kwargs={'template_id': self.id})
index 972592f..15d939c 100644 (file)
@@ -137,7 +137,7 @@ def update_job(job, proxy):
                 elapsed, job.job_id, elapsed_job, job.tasks.count(), elapsed_tasks, elapsed_py)
 
 
-def submit_job(params, proxy):
+def make_job_desc(params, proxy):
     QCG.start()
     desc = JobDescription(Credential(proxy))
 
@@ -174,9 +174,7 @@ def submit_job(params, proxy):
         desc.set_watch_output(params['watch_output'], params['watch_output_pattern'])
     # TODO monitoring
 
-    job = desc.submit()
-
-    return job.job_id
+    return desc
 
 
 def cancel(obj, proxy):
index 64cb5bf..b907fe3 100644 (file)
@@ -32,7 +32,7 @@
                         <li{% ifequal request.resolver_match.url_name 'jobs' %} class="active"{% endifequal %}>
                             <a href="{% url 'jobs' %}">Zadania</a>
                         </li>
-                        <li{% ifequal request.resolver_match.url_name 'job_submit' %} class="active"{% endifequal %}>
+                        <li{% if request.resolver_match.url_name == 'job_submit' or request.resolver_match.url_name == 'template_submit' %} class="active"{% endif %}>
                             <a href="{% url 'job_submit' %}">Zleć zadanie</a>
                         </li>
                         <li{% ifequal request.resolver_match.url_name 'gridftp' %} class="active"{% endifequal %}>
@@ -49,7 +49,7 @@
                                 </a>
                                 <ul class="dropdown-menu">
                                     <li><a href="{% url 'job_templates' %}">
-                                        <span class="glyphicon glyphicon-floppy-disk"></span>&nbsp;Zapisane zadania</a>
+                                        <span class="glyphicon glyphicon-floppy-disk"></span>&nbsp;Szablony zadania</a>
                                     </li>
 
                                     {% if request.user.is_superuser %}
index ecc3c72..5fabdc0 100644 (file)
             $('#gridftp').one('show.bs.modal', function() {
                 filex.initialLoad();
             });
+
+            $('#template').on('show.bs.modal', function() {
+                $(this).find('.alert').remove();
+                this.reset();
+            }).on('shown.bs.modal', function() {
+                $(this).find('input[type="text"]')[0].focus();
+            }).on('submit', function(e) {
+                e.preventDefault();
+
+                var $this = $(this),
+                    $btn = $this.find('[type="submit"]');
+
+                $this.find('.alert-danger').remove();
+                $btn.button('loading');
+
+                $.post(this.action, $('#description,#template').serializeArray(), function(response) {
+                    // FIXME url after redirection
+                    document.open();
+                    document.write(response);
+                    document.close();
+                }).fail(function(xhr) {
+                    var error = (xhr.responseJSON || {}).error || 'Błąd serwera';
+
+                    $('<div>', {
+                        'class': 'alert alert-danger',
+                        html: '<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> ' + error
+                    }).prependTo($this.find('.modal-body'));
+
+                    $btn.button('reset');
+                });
+            });
         });
     </script>
 {% endblock %}
 
 {% block container %}
-    <h1 class="page-header">{% block title %}Zleć zadanie{% endblock %}</h1>
+    <div class="pull-right">
+        {% if template %}
+            <button class="btn btn-default" data-toggle="modal" data-target="#template">Zapisz</button>
+            <button class="btn btn-default" data-toggle="modal" data-target="#delete-modal">Usuń</button>
+        {% else %}
+            <button class="btn btn-default" data-toggle="modal" data-target="#template">Zapisz jako szablon</button>
+        {% endif %}
+    </div>
+
+    <h1 class="page-header">{% block title %}Zleć zadanie{% endblock %} <small>{{ template.name }}</small></h1>
 
     {% if errors %}
         <div class="alert alert-danger">
             <strong>Uwaga!</strong> Formularz zawiera błędy.
             {{ form.non_field_errors }}
             {{ env_formset.non_field_errors }}
+            {{ template_form.non_field_errors }}
         </div>
     {% endif %}
 
-    <form action="." method="post" class="form-horizontal">
+    <form id="description" action="." method="post" class="form-horizontal">
         {% csrf_token %}
 
         <!-- Nav tabs -->
         </div>
     </form>
 
-    <div id="gridftp" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="modal-label" aria-hidden="true">
+    <div id="gridftp" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="gridftp-modal-label" aria-hidden="true">
         <div class="modal-dialog modal-lg">
             <div class="modal-content">
                 <div class="modal-header">
                     <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
-                    <h4 class="modal-title" id="modal-label">Wybierz plik</h4>
+                    <h4 class="modal-title" id="gridftp-modal-label">Wybierz plik</h4>
                 </div>
                 <div class="modal-body">
                     {% include 'filex/source.html' %}
         </div>
     </div>
 
+    <form id="template" action="." method="post" class="modal fade form-horizontal" tabindex="-1"
+          role="dialog" aria-labelledby="template-modal-label" aria-hidden="true">
+        <div class="modal-dialog">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                    <h4 class="modal-title" id="template-modal-label">Podaj nazwę szablonu</h4>
+                </div>
+                <div class="modal-body">
+                    {% csrf_token %}
+                    {% bootstrap_field template_form.name layout="horizontal" %}
+                    <input type="hidden" name="save-template" value="1">
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">Anuluj</button>
+                    <button type="submit" class="btn btn-primary" data-loading-text="Zapisywanie...">Zapisz</button>
+                </div>
+            </div>
+        </div>
+    </form>
+
+    {% if template %}
+        <form id="delete-modal" action="{% url 'template_delete' template.id %}" method="post" class="modal fade" tabindex="-1"
+              role="dialog" aria-labelledby="delete-modal-label" aria-hidden="true">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                        <h4 class="modal-title" id="delete-modal-label">Usuwanie szablonu</h4>
+                    </div>
+                    <div class="modal-body">
+                        {% csrf_token %}
+                        <p>Czy na pewno usunąć szablon <em>{{ template.name }}</em>?</p>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-default" data-dismiss="modal">Anuluj</button>
+                        <button type="submit" class="btn btn-danger">Usuń</button>
+                    </div>
+                </div>
+            </div>
+        </form>
+    {% endif %}
 {% endblock %}
index 7bdeea1..43c7846 100644 (file)
@@ -1,5 +1,19 @@
 {% extends 'qcg/base.html' %}
 
+{% block extra_js %}
+    <script>
+        $(function() {
+            $('.delete-template').on('click', function() {
+                var $this = $(this),
+                    $modal = $('#delete-modal');
+
+                $modal.attr('action', $this.data('url'));
+                $modal.find('em').text($this.data('name'));
+            });
+        });
+    </script>
+{% endblock %}
+
 {% block container %}
     <h1 class="page-header">{% block title %}Szablony zadania{% endblock %}</h1>
 
                 {% for template in templates %}
                     <tr>
                         <td class="text-right">{{ forloop.counter }}</td>
-                        <td><a href="#">{{ template.name }}</a></td>
+                        <td><a href="{{ template.get_absolute_url }}">{{ template.name }}</a></td>
                         <td>{{ template.created }}</td>
                         <td>{{ template.updated }}</td>
                         <td class="text-right">
-                            <button class="btn btn-xs btn-danger" title="Usuń">
+                            <button class="btn btn-xs btn-danger delete-template" title="Usuń"
+                                    data-url="{% url 'template_delete' template.id %}"
+                                    data-name="{{ template.name }}"
+                                    data-toggle="modal" data-target="#delete-modal">
                                 <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
                             </button>
                         </td>
         </div>
     {% endif %}
 
+    <form id="delete-modal" action="" method="post" class="modal fade" tabindex="-1"
+          role="dialog" aria-labelledby="delete-modal-label" aria-hidden="true">
+        <div class="modal-dialog">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                    <h4 class="modal-title" id="delete-modal-label">Usuwanie szablonu</h4>
+                </div>
+                <div class="modal-body">
+                    {% csrf_token %}
+                    <p>Czy na pewno usunąć szablon <em></em>?</p>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">Anuluj</button>
+                    <button type="submit" class="btn btn-danger">Usuń</button>
+                </div>
+            </div>
+        </div>
+    </form>
+
 {% endblock %}
index f3a99ab..57aea13 100644 (file)
@@ -16,6 +16,9 @@ urlpatterns = patterns('',
     url(r'^job/templates/$', views.job_templates, name='job_templates'),
 
     url(r'^job/submit/$', views.job_submit, name='job_submit'),
+    url(r'^job/template/submit/(?P<template_id>\d+)/$', views.job_submit, name='template_submit'),
+    url(r'^job/template/delete/(?P<template_id>\d+)/$', views.template_delete, name='template_delete'),
+
     url(r'^job/cancel/(?P<job_id>[\w]+)/$', views.job_cancel, name='job_cancel'),
     url(r'^job/clean/(?P<job_id>[\w]+)/$', views.job_clean, name='job_clean'),
     url(r'^task/cancel/(?P<job_id>[\w]+)/(?P<task_id>[\w]+)/$', views.task_cancel, name='task_cancel'),
index 973247b..5e6cd8e 100644 (file)
@@ -21,9 +21,9 @@ from pyqcg.utils.qcg_types import PyqcgException
 from filex.forms import HostPathNameForm, RenameForm, ArchiveForm, HostPathForm
 from filex.ftp import FTPOperation, FTPError
 from filex.views import make_url
-from qcg.forms import FiltersForm, ColumnsForm, JobDescriptionForm, EnvFormSet
+from qcg.forms import FiltersForm, ColumnsForm, JobDescriptionForm, EnvFormSet, JobTemplateForm
 from qcg.utils import paginator_context
-from qcg.service import update_user_data, submit_job, update_job, cancel, clean
+from qcg.service import update_user_data, make_job_desc, update_job, cancel, clean
 
 
 def index(request):
@@ -170,33 +170,58 @@ def task_details(request, job_id, task_id):
 
 
 @login_required
-def job_submit(request):
+def job_submit(request, template_id=None):
+    save_template = 'save-template' in request.POST
+    template = get_object_or_404(request.user.templates, id=template_id) if template_id is not None else None
+
     if request.method == 'POST':
         form = JobDescriptionForm(request.POST)
         env_formset = EnvFormSet(request.POST)
+        template_form = JobTemplateForm(request.POST, prefix='template', instance=template)
 
-        if form.is_valid() and env_formset.is_valid():
+        if form.is_valid() and env_formset.is_valid() and (not save_template or template_form.is_valid):
             params = form.cleaned_data
             params['env_variables'] = [(env['name'], env['value'])
                                        for env in env_formset.cleaned_data if env and not env['DELETE']]
 
-            job_id = submit_job(params, request.session['proxy'])
+            job_desc = make_job_desc(params, request.session['proxy'])
+
+            if save_template:
+                if template is not None:
+                    # save already existing template...
+                    instance = template_form.save()
+                else:
+                    # or set attributes for new template
+                    instance = template_form.save(commit=False)
+
+                    instance.owner = request.user
+                    instance.description = job_desc.xml_description
+                    instance.save()
+
+                return redirect(instance)
+
+            else:
+                job = job_desc.submit()
 
-            messages.success(request,
-                             format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
-                                         'Zlecono zadanie <em>{}</em>.', job_id))
+                messages.success(request,
+                                 format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
+                                             'Zlecono zadanie <em>{}</em>.', job.job_id))
 
-            return redirect('jobs')
+                return redirect('jobs')
 
-        print repr(form.errors)
-        print repr(env_formset.errors)
+        print 'form', repr(form.errors)
+        print 'env_formset', repr(env_formset.errors)
+        print 'template_form', repr(template_form.errors)
     else:
         form = JobDescriptionForm()
         env_formset = EnvFormSet()
+        template_form = JobTemplateForm(prefix='template', instance=template)
 
-    errors = form.errors or (env_formset.is_bound and not env_formset.is_valid)
+    errors = form.errors or (env_formset.is_bound and not env_formset.is_valid) or (
+        save_template and template_form.errors)
 
-    return render(request, 'qcg/job_submit.html', {'form': form, 'env_formset': env_formset, 'errors': errors})
+    return render(request, 'qcg/job_submit.html', {'form': form, 'env_formset': env_formset, 'errors': errors,
+                                                   'template_form': template_form, 'template': template})
 
 
 @require_POST
@@ -277,3 +302,11 @@ def gridftp_upload(request):
 @login_required
 def job_templates(request):
     return render(request, 'qcg/job_templates.html', {'templates': request.user.templates.all()})
+
+
+@require_POST
+@login_required
+def template_delete(request, template_id):
+    get_object_or_404(request.user.templates, id=template_id).delete()
+
+    return redirect('job_templates')