5e8f0a001b9760cf8e84718d876941238779937e
[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 def generate_mremd_inputfile(params, mremd_input):
191     mremd_input = list()
192     opis=params['note']
193     mremd_input.append(opis[:73])
194     mremd_input.append('SEED=-3059743 PDBREF MD EXTCONF RESCALE_MODE=2')
195     
196     
197     
198     
199     return mremd_input
200
201 @login_required
202 def job_submit(request, template_id=None):
203     save_template = 'save-template' in request.POST
204     template = get_object_or_404(request.user.templates, id=template_id) if template_id is not None else None
205
206     if template is not None:
207         form_data = to_form_data(template.description)
208         env_formset_data = [{'name': name, 'value': value} for name, value in form_data.pop('env_variables', ())]
209     else:
210         form_data, env_formset_data = None, None
211
212     if request.method == 'POST':
213         form = JobDescriptionForm(request.POST, initial=form_data)
214         env_formset = EnvFormSet(request.POST, initial=env_formset_data)
215         template_form = JobTemplateForm(request.POST, prefix='template', instance=template)
216
217         if form.is_valid() and env_formset.is_valid() and (not save_template or template_form.is_valid()):
218             params = form.cleaned_data
219             params['env_variables'] = [(env['name'], env['value'])
220                                        for env in env_formset.cleaned_data if env and not env['DELETE']]
221
222             # tu wygenerować plik inputowy i go uploadować?
223             mremd_input=list()
224             print generate_mremd_inputfile(params,mremd_input)
225             
226
227             params['persistent'] = True
228             # Debugging parametrów
229             pprint(params)
230
231             job_desc = to_job_desc(params, request.session['proxy'])
232
233             if save_template:
234                 template = template_form.save(commit=False)
235
236                 template.owner = request.user
237                 template.description = job_desc.xml_description
238                 template.save()
239
240                 return redirect(template)
241
242             # wyłączenie wysyłania zadania
243             '''
244             job = job_desc.submit()
245
246             messages.success(request,
247                              format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
248                                          u'Zlecono zadanie <em>{}</em>.', job.job_id))
249             '''
250             return redirect('jobs')
251     else:
252         form = JobDescriptionForm(initial=form_data)
253         env_formset = EnvFormSet(initial=env_formset_data)
254         template_form = JobTemplateForm(prefix='template', instance=template)
255
256     errors = form.errors or (env_formset.is_bound and not env_formset.is_valid) or (
257         save_template and template_form.errors)
258
259     return render(request, 'qcg/job_submit.html', {'form': form, 'env_formset': env_formset, 'errors': errors,
260                                                    'template_form': template_form, 'template': template})
261
262
263 @require_POST
264 @login_required
265 def job_cancel(request, job_id):
266     return obj_cancel(request, get_object_or_404(request.user.jobs, job_id=job_id))
267
268
269 @require_POST
270 @login_required
271 def task_cancel(request, job_id, task_id):
272     return obj_cancel(request, get_object_or_404(request.user.tasks, job__job_id=job_id, task_id=task_id))
273
274
275 def obj_cancel(request, obj):
276     try:
277         cancel(obj, request.session['proxy'])
278     except PyqcgException as e:
279         messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}',
280                                             e.message))
281     else:
282         messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
283                                               'Zadanie anulowano.'))
284
285     return redirect(obj)
286
287
288 @require_POST
289 @login_required
290 def job_clean(request, job_id):
291     return obj_clean(request, get_object_or_404(request.user.jobs, job_id=job_id))
292
293
294 @require_POST
295 @login_required
296 def task_clean(request, job_id, task_id):
297     return obj_clean(request, get_object_or_404(request.user.tasks, job__job_id=job_id, task_id=task_id))
298
299
300 def obj_clean(request, obj):
301     try:
302         clean(obj, request.session['proxy'])
303     except PyqcgException as e:
304         messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}',
305                                             e.message))
306     else:
307         messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
308                                               'Usunięto katalog roboczy.'))
309
310     return redirect(obj)
311
312
313 @login_required
314 def gridftp(request):
315     return render(request, 'qcg/gridftp.html',
316                   {'new_dir_form': HostPathNameForm(), 'rename_form': RenameForm(), 'archive_form': ArchiveForm()})
317
318
319 @login_required
320 def gridftp_upload(request):
321     form = HostPathForm(request.GET)
322
323     if not form.is_valid():
324         raise SuspiciousOperation('Invalid parameters for `gridftp_upload`!')
325
326     error = None
327     try:
328         FTPOperation(request.session['proxy']).info(make_url(form.cleaned_data, 'path'))
329     except FTPError as e:
330         error = e.message
331
332     return render(request, 'qcg/gridftp_upload.html',
333                   {'error': error, 'url': reverse('filex:upload') + '?' + urlencode(form.cleaned_data),
334                    'host': form.cleaned_data['host'], 'path': form.cleaned_data['path'],
335                    'sep': '/' if form.cleaned_data['path'].startswith('~') else ''})
336
337
338 @login_required
339 def job_templates(request):
340     return render(request, 'qcg/job_templates.html', {'templates': request.user.templates.all()})
341
342
343 @require_POST
344 @login_required
345 def template_delete(request, template_id):
346     template = get_object_or_404(request.user.templates, id=template_id)
347     template.delete()
348
349     messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
350                                           u'Usunięto szablon <em>{}</em>.', template.name))
351
352     return redirect('job_templates')
353
354
355 @require_POST
356 @login_required
357 def job_save_template(request, job_id):
358     job = get_object_or_404(request.user.jobs, job_id=job_id)
359
360     template_form = JobTemplateForm(request.POST,
361                                     instance=JobTemplate(owner=request.user, description=job.qcg_description))
362
363     if template_form.is_valid():
364         template = template_form.save()
365
366         return redirect(template)
367
368     messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}'
369                                         'Nie udało się zapisać szablonu!', template_form.errors))
370
371     return redirect(job)