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