dfeebd3787fae5354e456550664f7ff4a3d797c9
[qcg-portal.git] / qcg / views.py
1 # coding=utf-8
2 from datetime import datetime, timedelta
3
4 from django.conf import settings
5 from django.contrib import messages
6 from django.contrib.auth import REDIRECT_FIELD_NAME
7 from django.contrib.auth.decorators import login_required
8 from django.core.exceptions import SuspiciousOperation
9 from django.core.urlresolvers import reverse
10 from django.db.models import Q
11 from django.http import HttpResponse, QueryDict
12 from django.shortcuts import render, get_object_or_404, redirect
13 from django.utils.html import format_html
14 from django.utils.http import urlencode
15 from django.utils.timezone import UTC
16 from django.views.decorators.http import require_POST
17 from django_openid_auth.views import make_consumer
18 from openid.extensions import ax
19 from pyqcg.utils import PyqcgException
20
21 from filex.forms import HostPathNameForm, RenameForm, ArchiveForm, HostPathForm
22 from filex.ftp import FTPOperation, FTPError
23 from filex.views import make_url
24 from qcg.forms import FiltersForm, ColumnsForm, JobDescriptionForm, EnvFormSet, JobTemplateForm
25 from qcg.models import JobTemplate
26 from qcg.utils import paginator_context, to_job_desc, to_form_data
27 from qcg.service import update_user_data, update_job, cancel, clean
28
29 # for Debugging
30 from pprint import pprint
31
32 def index(request):
33     return render(request, 'qcg/base.html')
34
35
36 def openid_begin(request):
37     openid_request = make_consumer(request).begin(settings.OPENID_SSO_SERVER_URL)
38
39     fetch_request = ax.FetchRequest()
40     for (attr, alias) in [('http://axschema.org/namePerson/friendly', 'nickname'),
41                           ('http://axschema.org/contact/email', 'email'),
42                           ('http://axschema.org/namePerson', 'fullname'),
43                           ('http://openid.plgrid.pl/certificate/proxy', 'proxy'),
44                           ('http://openid.plgrid.pl/certificate/userCert', 'userCert'),
45                           ('http://openid.plgrid.pl/certificate/proxyPrivKey', 'proxyPrivKey'),
46                           ('http://openid.plgrid.pl/POSTresponse', 'POSTresponse')]:
47         fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True))
48     openid_request.addExtension(fetch_request)
49
50     return_to = request.build_absolute_uri(reverse('openid-complete'))
51
52     redirect_to = request.GET.get(REDIRECT_FIELD_NAME)
53     if redirect_to:
54         return_to += '?' + urlencode({REDIRECT_FIELD_NAME: redirect_to})
55
56     return HttpResponse(openid_request.htmlMarkup(request.build_absolute_uri('/'), return_to))
57
58
59 search_fields = ('status_description', 'type', 'note', 'task_id', 'job__note', 'job__project', 'job__job_id',
60                  'allocations__status_description', 'allocations__processes_group_id', 'allocations__comment')
61
62
63 def parse_date(string):
64     return datetime.strptime(string.strip(), "%d.%m.%Y").replace(tzinfo=UTC())
65
66
67 @login_required
68 def jobs_list(request):
69     update_user_data(request.user, request.session['proxy'])
70
71     tasks = request.user.tasks.order_by('-job__submission_time', '-submission_time') \
72         .select_related('job').prefetch_related('allocations__nodes')
73
74     filters = FiltersForm(request.GET)
75     selected_filters = []
76     if filters.is_valid():
77         keywords = filters.cleaned_data['keywords']
78         status = filters.cleaned_data['status']
79         host = filters.cleaned_data['host']
80         purged = filters.cleaned_data['purged']
81         submission = filters.cleaned_data['submission']
82         finish = filters.cleaned_data['finish']
83
84         if keywords:
85             and_query = Q()
86
87             for q in keywords.split():
88                 or_query = Q()
89                 for field in search_fields:
90                     or_query |= Q(**{field + '__icontains': q})
91                 and_query &= or_query
92
93             tasks = tasks.filter(and_query)
94             selected_filters.append((keywords, 'keywords', keywords))
95
96         if status:
97             statuses = []
98             for s in status:
99                 si = int(s)
100                 statuses.extend(FiltersForm.STATUS_MAP[si])
101                 selected_filters.append((FiltersForm.STATUS_CHOICES_DICT[si], 'status', s))
102
103             tasks = tasks.filter(status__in=statuses)
104
105         if host:
106             tasks = tasks.filter(allocations__host_name__in=host)
107
108             host_dict = dict(filters.fields['host'].choices)
109             for h in host:
110                 selected_filters.append((host_dict[h], 'host', h))
111
112         if isinstance(purged, bool):
113             tasks = tasks.filter(purged=purged)
114
115             if purged:
116                 selected_filters.append((u"Usunięty katalog roboczy", 'purged', '1'))
117             else:
118                 selected_filters.append((u"Istniejący katalog roboczy", 'purged', '0'))
119
120         if submission:
121             start, end = submission.split('-')
122
123             tasks = tasks.filter(submission_time__gte=parse_date(start),
124                                  submission_time__lte=parse_date(end) + timedelta(days=1))
125             selected_filters.append((u'Data zlecenia: ' + submission, 'submission', submission))
126
127         if finish:
128             start, end = finish.split('-')
129
130             tasks = tasks.filter(finish_time__gte=parse_date(start),
131                                  finish_time__lte=parse_date(end) + timedelta(days=1))
132             selected_filters.append((u'Data zakończenia: ' + finish, 'finish', finish))
133
134         tasks = tasks.distinct()
135
136     checked_status = {i: widget.is_checked() for i, widget in enumerate(filters['status'])}
137
138     columns = ColumnsForm(QueryDict(request.COOKIES.get('columns', '')))
139
140     displayed = None
141     if columns.is_valid():
142         displayed = {int(c) for c in columns.cleaned_data['columns']}
143
144     # if invalid or empty
145     if not displayed:
146         displayed = set(columns.fields['columns'].initial)
147
148     context = {'filters': filters, 'checked_status': checked_status, 'selected_filters': selected_filters,
149                'columns': ColumnsForm(initial={'columns': displayed}), 'displayed': displayed}
150
151     context.update(paginator_context(request, tasks))
152
153     return render(request, 'qcg/jobs.html', context)
154
155
156 @login_required
157 def job_details(request, job_id):
158     job = get_object_or_404(request.user.jobs.prefetch_related('tasks'), job_id=job_id)
159
160     return render(request, 'qcg/job.html', process_details(request, job))
161
162
163 @login_required
164 def task_details(request, job_id, task_id):
165     task = get_object_or_404(request.user.tasks.select_related('job').prefetch_related('allocations'),
166                              job__job_id=job_id, task_id=task_id)
167
168     return render(request, 'qcg/task.html', process_details(request, task.job, task))
169
170
171 def process_details(request, job, task=None):
172     if not job.terminated or not job.purged:
173         update_job(job, request.session['proxy'])
174
175     form_data = to_form_data(job.qcg_description)
176     env_formset_data = [{'name': name, 'value': value} for name, value in form_data.pop('env_variables', ())]
177
178     form = JobDescriptionForm(initial=form_data)
179     env_formset = EnvFormSet(initial=env_formset_data)
180     template_form = JobTemplateForm(initial={'name': (task and task.note) or u'Szablon z {}'.format(job.job_id)})
181
182     for field in form:
183         field.field.widget.attrs['disabled'] = 'True'
184     for env_form in env_formset:
185         for field in env_form:
186             field.field.widget.attrs['disabled'] = 'True'
187
188     return {'job': job, 'task': task, 'form': form, 'env_formset': env_formset, 'template_form': template_form}
189
190
191 @login_required
192 def job_submit(request, template_id=None):
193     save_template = 'save-template' in request.POST
194     template = get_object_or_404(request.user.templates, id=template_id) if template_id is not None else None
195
196     if template is not None:
197         form_data = to_form_data(template.description)
198         env_formset_data = [{'name': name, 'value': value} for name, value in form_data.pop('env_variables', ())]
199     else:
200         form_data, env_formset_data = None, None
201
202     if request.method == 'POST':
203         form = JobDescriptionForm(request.POST, initial=form_data)
204         env_formset = EnvFormSet(request.POST, initial=env_formset_data)
205         template_form = JobTemplateForm(request.POST, prefix='template', instance=template)
206
207         if form.is_valid() and env_formset.is_valid() and (not save_template or template_form.is_valid()):
208             params = form.cleaned_data
209             params['env_variables'] = [(env['name'], env['value'])
210                                        for env in env_formset.cleaned_data if env and not env['DELETE']]
211
212             # tu wygenerować plik inputowy i go uploadować?
213
214             params['persistent'] = True
215             # Debugging parametrów
216             pprint(params)
217
218             job_desc = to_job_desc(params, request.session['proxy'])
219
220             if save_template:
221                 template = template_form.save(commit=False)
222
223                 template.owner = request.user
224                 template.description = job_desc.xml_description
225                 template.save()
226
227                 return redirect(template)
228
229             # wyłączenie wysyłania zadania
230             '''
231             job = job_desc.submit()
232
233             messages.success(request,
234                              format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
235                                          u'Zlecono zadanie <em>{}</em>.', job.job_id))
236             '''
237             return redirect('jobs')
238     else:
239         form = JobDescriptionForm(initial=form_data)
240         env_formset = EnvFormSet(initial=env_formset_data)
241         template_form = JobTemplateForm(prefix='template', instance=template)
242
243     errors = form.errors or (env_formset.is_bound and not env_formset.is_valid) or (
244         save_template and template_form.errors)
245
246     return render(request, 'qcg/job_submit.html', {'form': form, 'env_formset': env_formset, 'errors': errors,
247                                                    'template_form': template_form, 'template': template})
248
249
250 @require_POST
251 @login_required
252 def job_cancel(request, job_id):
253     return obj_cancel(request, get_object_or_404(request.user.jobs, job_id=job_id))
254
255
256 @require_POST
257 @login_required
258 def task_cancel(request, job_id, task_id):
259     return obj_cancel(request, get_object_or_404(request.user.tasks, job__job_id=job_id, task_id=task_id))
260
261
262 def obj_cancel(request, obj):
263     try:
264         cancel(obj, request.session['proxy'])
265     except PyqcgException as e:
266         messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}',
267                                             e.message))
268     else:
269         messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
270                                               'Zadanie anulowano.'))
271
272     return redirect(obj)
273
274
275 @require_POST
276 @login_required
277 def job_clean(request, job_id):
278     return obj_clean(request, get_object_or_404(request.user.jobs, job_id=job_id))
279
280
281 @require_POST
282 @login_required
283 def task_clean(request, job_id, task_id):
284     return obj_clean(request, get_object_or_404(request.user.tasks, job__job_id=job_id, task_id=task_id))
285
286
287 def obj_clean(request, obj):
288     try:
289         clean(obj, request.session['proxy'])
290     except PyqcgException as e:
291         messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}',
292                                             e.message))
293     else:
294         messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
295                                               'Usunięto katalog roboczy.'))
296
297     return redirect(obj)
298
299
300 @login_required
301 def gridftp(request):
302     return render(request, 'qcg/gridftp.html',
303                   {'new_dir_form': HostPathNameForm(), 'rename_form': RenameForm(), 'archive_form': ArchiveForm()})
304
305
306 @login_required
307 def gridftp_upload(request):
308     form = HostPathForm(request.GET)
309
310     if not form.is_valid():
311         raise SuspiciousOperation('Invalid parameters for `gridftp_upload`!')
312
313     error = None
314     try:
315         FTPOperation(request.session['proxy']).info(make_url(form.cleaned_data, 'path'))
316     except FTPError as e:
317         error = e.message
318
319     return render(request, 'qcg/gridftp_upload.html',
320                   {'error': error, 'url': reverse('filex:upload') + '?' + urlencode(form.cleaned_data),
321                    'host': form.cleaned_data['host'], 'path': form.cleaned_data['path'],
322                    'sep': '/' if form.cleaned_data['path'].startswith('~') else ''})
323
324
325 @login_required
326 def job_templates(request):
327     return render(request, 'qcg/job_templates.html', {'templates': request.user.templates.all()})
328
329
330 @require_POST
331 @login_required
332 def template_delete(request, template_id):
333     template = get_object_or_404(request.user.templates, id=template_id)
334     template.delete()
335
336     messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
337                                           u'Usunięto szablon <em>{}</em>.', template.name))
338
339     return redirect('job_templates')
340
341
342 @require_POST
343 @login_required
344 def job_save_template(request, job_id):
345     job = get_object_or_404(request.user.jobs, job_id=job_id)
346
347     template_form = JobTemplateForm(request.POST,
348                                     instance=JobTemplate(owner=request.user, description=job.qcg_description))
349
350     if template_form.is_valid():
351         template = template_form.save()
352
353         return redirect(template)
354
355     messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}'
356                                         'Nie udało się zapisać szablonu!', template_form.errors))
357
358     return redirect(job)