disabled moonitoring tab
[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
30 from django.utils import encoding
31 import string
32 import random
33 import os
34 from qcg import constants
35 from qcg.utils import chunks
36 # for Debugging
37 from pprint import pprint
38
39 def index(request):
40     return render(request, 'qcg/base.html')
41
42
43 def openid_begin(request):
44     openid_request = make_consumer(request).begin(settings.OPENID_SSO_SERVER_URL)
45
46     fetch_request = ax.FetchRequest()
47     for (attr, alias) in [('http://axschema.org/namePerson/friendly', 'nickname'),
48                           ('http://axschema.org/contact/email', 'email'),
49                           ('http://axschema.org/namePerson', 'fullname'),
50                           ('http://openid.plgrid.pl/certificate/proxy', 'proxy'),
51                           ('http://openid.plgrid.pl/certificate/userCert', 'userCert'),
52                           ('http://openid.plgrid.pl/certificate/proxyPrivKey', 'proxyPrivKey'),
53                           ('http://openid.plgrid.pl/POSTresponse', 'POSTresponse')]:
54         fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True))
55     openid_request.addExtension(fetch_request)
56
57     return_to = request.build_absolute_uri(reverse('openid-complete'))
58
59     redirect_to = request.GET.get(REDIRECT_FIELD_NAME)
60     if redirect_to:
61         return_to += '?' + urlencode({REDIRECT_FIELD_NAME: redirect_to})
62
63     return HttpResponse(openid_request.htmlMarkup(request.build_absolute_uri('/'), return_to))
64
65
66 search_fields = ('status_description', 'type', 'note', 'task_id', 'job__note', 'job__project', 'job__job_id',
67                  'allocations__status_description', 'allocations__processes_group_id', 'allocations__comment')
68
69
70 def parse_date(string):
71     return datetime.strptime(string.strip(), "%d.%m.%Y").replace(tzinfo=UTC())
72
73
74 @login_required
75 def jobs_list(request):
76     update_user_data(request.user, request.session['proxy'])
77
78     tasks = request.user.tasks.order_by('-job__submission_time', '-submission_time') \
79         .select_related('job').prefetch_related('allocations__nodes')
80
81     filters = FiltersForm(request.GET)
82     selected_filters = []
83     if filters.is_valid():
84         keywords = filters.cleaned_data['keywords']
85         status = filters.cleaned_data['status']
86         host = filters.cleaned_data['host']
87         purged = filters.cleaned_data['purged']
88         submission = filters.cleaned_data['submission']
89         finish = filters.cleaned_data['finish']
90
91         if keywords:
92             and_query = Q()
93
94             for q in keywords.split():
95                 or_query = Q()
96                 for field in search_fields:
97                     or_query |= Q(**{field + '__icontains': q})
98                 and_query &= or_query
99
100             tasks = tasks.filter(and_query)
101             selected_filters.append((keywords, 'keywords', keywords))
102
103         if status:
104             statuses = []
105             for s in status:
106                 si = int(s)
107                 statuses.extend(FiltersForm.STATUS_MAP[si])
108                 selected_filters.append((FiltersForm.STATUS_CHOICES_DICT[si], 'status', s))
109
110             tasks = tasks.filter(status__in=statuses)
111
112         if host:
113             tasks = tasks.filter(allocations__host_name__in=host)
114
115             host_dict = dict(filters.fields['host'].choices)
116             for h in host:
117                 selected_filters.append((host_dict[h], 'host', h))
118
119         if isinstance(purged, bool):
120             tasks = tasks.filter(purged=purged)
121
122             if purged:
123                 selected_filters.append((u"Usunięty katalog roboczy", 'purged', '1'))
124             else:
125                 selected_filters.append((u"Istniejący katalog roboczy", 'purged', '0'))
126
127         if submission:
128             start, end = submission.split('-')
129
130             tasks = tasks.filter(submission_time__gte=parse_date(start),
131                                  submission_time__lte=parse_date(end) + timedelta(days=1))
132             selected_filters.append((u'Data zlecenia: ' + submission, 'submission', submission))
133
134         if finish:
135             start, end = finish.split('-')
136
137             tasks = tasks.filter(finish_time__gte=parse_date(start),
138                                  finish_time__lte=parse_date(end) + timedelta(days=1))
139             selected_filters.append((u'Data zakończenia: ' + finish, 'finish', finish))
140
141         tasks = tasks.distinct()
142
143     checked_status = {i: widget.is_checked() for i, widget in enumerate(filters['status'])}
144
145     columns = ColumnsForm(QueryDict(request.COOKIES.get('columns', '')))
146
147     displayed = None
148     if columns.is_valid():
149         displayed = {int(c) for c in columns.cleaned_data['columns']}
150
151     # if invalid or empty
152     if not displayed:
153         displayed = set(columns.fields['columns'].initial)
154
155     context = {'filters': filters, 'checked_status': checked_status, 'selected_filters': selected_filters,
156                'columns': ColumnsForm(initial={'columns': displayed}), 'displayed': displayed}
157
158     context.update(paginator_context(request, tasks))
159
160     return render(request, 'qcg/jobs.html', context)
161
162
163 @login_required
164 def job_details(request, job_id):
165     job = get_object_or_404(request.user.jobs.prefetch_related('tasks'), job_id=job_id)
166
167     return render(request, 'qcg/job.html', process_details(request, job))
168
169
170 @login_required
171 def task_details(request, job_id, task_id):
172     task = get_object_or_404(request.user.tasks.select_related('job').prefetch_related('allocations'),
173                              job__job_id=job_id, task_id=task_id)
174
175     return render(request, 'qcg/task.html', process_details(request, task.job, task))
176
177
178 def process_details(request, job, task=None):
179     if not job.terminated or not job.purged:
180         update_job(job, request.session['proxy'])
181
182     form_data = to_form_data(job.qcg_description)
183     env_formset_data = [{'name': name, 'value': value} for name, value in form_data.pop('env_variables', ())]
184
185     form = JobDescriptionForm(initial=form_data)
186     env_formset = EnvFormSet(initial=env_formset_data)
187     template_form = JobTemplateForm(initial={'name': (task and task.note) or u'Szablon z {}'.format(job.job_id)})
188
189     for field in form:
190         field.field.widget.attrs['disabled'] = 'True'
191     for env_form in env_formset:
192         for field in env_form:
193             field.field.widget.attrs['disabled'] = 'True'
194
195     return {'job': job, 'task': task, 'form': form, 'env_formset': env_formset, 'template_form': template_form}
196
197 def generate_md_inputfile(params):
198     md_input = list()
199     # Opis pliku wyjsciowego
200     opis=params['note'][:80]
201     md_input.append(encoding.smart_str(opis, encoding='ascii', errors='ignore'))
202     # Dane kontrolne obliczeń
203     md_input.append('SEED=-3059743 PDBREF MD EXTCONF RESCALE_MODE=2')
204     ctl_data='nstep='+str(params['nstep'])+' ntwe='+str(params['ntwe'])
205     ctl_data+=' ntwx='+str(params['ntwx'])+' dt='+str(params['dt'])+' damax='+str(params['damax'])+'lang=0 tbf'
206     md_input.append('{:<79}&'.format(ctl_data))
207     md_input.append('tau_bath=1.0 t_bath=300 reset_vel=10000 respa ntime_split=1 maxtime_split=512')
208     # Paramatry pól siłowych
209     if params['force_field'] == 'GAB':
210         # Wagi pola GAB
211         md_input.append('WLONG=1.35279 WSCP=1.59304 WELEC=0.71534 WBOND=1.00000 WANG=1.13873            &')
212         md_input.append('WSCLOC=0.16258 WTOR=1.98599 WTORD=1.57069 WCORRH=0.42887 WCORR5=0.00000        &')
213         md_input.append('WCORR6=0.00000 WEL_LOC=0.16036 WTURN3=1.68722 WTURN4=0.66230 WTURN6=0.00000    &')
214         md_input.append('WVDWPP=0.11371 WHPB=1.00000                                                    &')
215         md_input.append('CUTOFF=7.00000 WCORR4=0.00000 WSCCOR=0.0')
216     else:
217         # Wagi pola E0LLY
218         md_input.append('WLONG=1.00000 WSCP=1.23315 WELEC=0.84476 WBOND=1.00000 WANG=0.62954            &')
219         md_input.append('WSCLOC=0.10554 WTOR=1.84316 WTORD=1.26571 WCORRH=0.19212 WCORR5=0.00000        &')
220         md_input.append('WCORR6=0.00000 WEL_LOC=0.37357 WTURN3=1.40323 WTURN4=0.64673 WTURN6=0.00000    &')
221         md_input.append('WVDWPP=0.23173 WHPB=1.00000 WSCCOR=0.0                                         &')
222         md_input.append('CUTOFF=7.00000 WCORR4=0.00000')
223     # Plik PDB    
224     md_input.append(params['pdb_file'].split('/')[-1])
225     # Sekwencja aminokwasów
226     md_input.append(len(params['sequence']))
227     seq_str=params['sequence']
228     while seq_str:
229         md_input.append(seq_str[:80])
230         seq_str=seq_str[80:]
231     md_input.append(' 0')
232     md_input.append(' 0')
233     
234     return md_input
235
236
237 def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
238     return ''.join(random.choice(chars) for _ in range(size))
239
240 @login_required
241 def job_submit(request, template_id=None):
242     save_template = 'save-template' in request.POST
243     template = get_object_or_404(request.user.templates, id=template_id) if template_id is not None else None
244
245     if template is not None:
246         form_data = to_form_data(template.description)
247         env_formset_data = [{'name': name, 'value': value} for name, value in form_data.pop('env_variables', ())]
248     else:
249         form_data, env_formset_data = None, None
250
251     if request.method == 'POST':
252         form = JobDescriptionForm(request.POST, initial=form_data)
253         env_formset = EnvFormSet(request.POST, initial=env_formset_data)
254         template_form = JobTemplateForm(request.POST, prefix='template', instance=template)
255
256         if form.is_valid() and env_formset.is_valid() and (not save_template or template_form.is_valid()):
257             params = form.cleaned_data
258             params['env_variables'] = [(env['name'], env['value'])
259                                        for env in env_formset.cleaned_data if env and not env['DELETE']]
260
261             # Generowanie pliku inputowego
262             md_input=generate_md_inputfile(params)
263             url=os.path.splitext(params['pdb_file'])[0]+'_MD_genarated.inp'
264             params['master_file']=url
265             # Upload
266             ftp = FTPOperation(request.session['proxy'])
267             ftp.put(url)
268             for chunk in chunks('\n'.join([str(i) for i in md_input] ), 4096):
269                 ftp.stream.put(chunk)
270             ftp.stream.put(None)
271             ftp.wait()
272             
273
274             params['persistent'] = True
275             # Debugging parametrów
276             #pprint(params)
277
278             job_desc = to_job_desc(params, request.session['proxy'])
279
280             if save_template:
281                 template = template_form.save(commit=False)
282
283                 template.owner = request.user
284                 template.description = job_desc.xml_description
285                 template.save()
286
287                 return redirect(template)
288
289             # wyłączenie wysyłania zadania - zakomentować poniżej
290             
291             job = job_desc.submit()
292
293             messages.success(request,
294                              format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
295                                          u'Zlecono zadanie <em>{}</em>.', job.job_id))
296             
297             return redirect('jobs')
298     else:
299         form = JobDescriptionForm(initial=form_data)
300         env_formset = EnvFormSet(initial=env_formset_data)
301         template_form = JobTemplateForm(prefix='template', instance=template)
302
303     errors = form.errors or (env_formset.is_bound and not env_formset.is_valid) or (
304         save_template and template_form.errors)
305
306     return render(request, 'qcg/job_submit.html', {'form': form, 'env_formset': env_formset, 'errors': errors,
307                                                    'template_form': template_form, 'template': template})
308
309
310 @require_POST
311 @login_required
312 def job_cancel(request, job_id):
313     return obj_cancel(request, get_object_or_404(request.user.jobs, job_id=job_id))
314
315
316 @require_POST
317 @login_required
318 def task_cancel(request, job_id, task_id):
319     return obj_cancel(request, get_object_or_404(request.user.tasks, job__job_id=job_id, task_id=task_id))
320
321
322 def obj_cancel(request, obj):
323     try:
324         cancel(obj, request.session['proxy'])
325     except PyqcgException as e:
326         messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}',
327                                             e.message))
328     else:
329         messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
330                                               'Zadanie anulowano.'))
331
332     return redirect(obj)
333
334
335 @require_POST
336 @login_required
337 def job_clean(request, job_id):
338     return obj_clean(request, get_object_or_404(request.user.jobs, job_id=job_id))
339
340
341 @require_POST
342 @login_required
343 def task_clean(request, job_id, task_id):
344     return obj_clean(request, get_object_or_404(request.user.tasks, job__job_id=job_id, task_id=task_id))
345
346
347 def obj_clean(request, obj):
348     try:
349         clean(obj, request.session['proxy'])
350     except PyqcgException as e:
351         messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}',
352                                             e.message))
353     else:
354         messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
355                                               'Usunięto katalog roboczy.'))
356
357     return redirect(obj)
358
359
360 @login_required
361 def gridftp(request):
362     return render(request, 'qcg/gridftp.html',
363                   {'new_dir_form': HostPathNameForm(), 'rename_form': RenameForm(), 'archive_form': ArchiveForm()})
364
365
366 @login_required
367 def gridftp_upload(request):
368     form = HostPathForm(request.GET)
369
370     if not form.is_valid():
371         raise SuspiciousOperation('Invalid parameters for `gridftp_upload`!')
372
373     error = None
374     try:
375         FTPOperation(request.session['proxy']).info(make_url(form.cleaned_data, 'path'))
376     except FTPError as e:
377         error = e.message
378
379     return render(request, 'qcg/gridftp_upload.html',
380                   {'error': error, 'url': reverse('filex:upload') + '?' + urlencode(form.cleaned_data),
381                    'host': form.cleaned_data['host'], 'path': form.cleaned_data['path'],
382                    'sep': '/' if form.cleaned_data['path'].startswith('~') else ''})
383
384
385 @login_required
386 def job_templates(request):
387     return render(request, 'qcg/job_templates.html', {'templates': request.user.templates.all()})
388
389
390 @require_POST
391 @login_required
392 def template_delete(request, template_id):
393     template = get_object_or_404(request.user.templates, id=template_id)
394     template.delete()
395
396     messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
397                                           u'Usunięto szablon <em>{}</em>.', template.name))
398
399     return redirect('job_templates')
400
401
402 @require_POST
403 @login_required
404 def job_save_template(request, job_id):
405     job = get_object_or_404(request.user.jobs, job_id=job_id)
406
407     template_form = JobTemplateForm(request.POST,
408                                     instance=JobTemplate(owner=request.user, description=job.qcg_description))
409
410     if template_form.is_valid():
411         template = template_form.save()
412
413         return redirect(template)
414
415     messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}'
416                                         'Nie udało się zapisać szablonu!', template_form.errors))
417
418     return redirect(job)