initial attempt to use django restframework
[htsworkflow.git] / samples / views.py
1 from __future__ import absolute_import, print_function, unicode_literals
2
3 # Create your views here.
4 import logging
5 import os
6 import json
7
8 from django.views.decorators.csrf import csrf_exempt
9 from django.http import HttpResponse, HttpResponseRedirect, Http404
10 from django.shortcuts import render_to_response, get_object_or_404
11 from django.template import RequestContext
12 from django.template.loader import get_template
13 from django.contrib.auth.decorators import login_required
14
15 from rest_framework import viewsets
16 from rest_framework.response import Response
17 from rest_framework.renderers import (TemplateHTMLRenderer,
18                                       JSONRenderer,
19                                       BrowsableAPIRenderer)
20
21 from htsworkflow.auth import require_api_key
22 from experiments.models import FlowCell, LANE_STATUS_MAP
23 from .changelist import HTSChangeList
24 from .models import Antibody, Library, Species
25 from .admin import LibraryOptions
26 from .results import get_flowcell_result_dict
27 from .serializers import (ExperimentTypeSerializer,
28                           LibrarySerializer,
29                           SpeciesSerializer)
30
31 from bcmagic.forms import BarcodeMagicForm
32 from htsworkflow.pipelines import runfolder
33 from htsworkflow.pipelines.samplekey import SampleKey
34 from htsworkflow.util.conversion import str_or_none, parse_flowcell_id
35 from htsworkflow.util import makebed
36 from htsworkflow.util import opener
37
38
39 LANE_LIST = [1, 2, 3, 4, 5, 6, 7, 8]
40 SAMPLES_CONTEXT_DEFAULTS = {
41     'app_name': 'Flowcell/Library Tracker',
42     'bcmagic': BarcodeMagicForm()
43 }
44
45 LOGGER = logging.getLogger(__name__)
46
47
48 def library(request, todo_only=False):
49     #filters = {'hidden__exact': 0}
50     if todo_only:
51         filters['lane'] = None
52
53     fcl = HTSChangeList(request, Library,
54                         list_filter=['affiliations', 'library_species'],
55                         search_fields=['id', 'library_name', 'amplified_from_sample__id'],
56                         list_per_page=200,
57                         model_admin=LibraryOptions(Library, None),
58                         )
59
60     context = {'cl': fcl,
61                'library_list': fcl.result_list,
62                'title': 'Library Index',
63                'todo_only': todo_only}
64
65     t = get_template('samples/library_index.html')
66     c = RequestContext(request, context)
67     return HttpResponse(t.render(c))
68
69
70 def library_not_run(request):
71     return library(request, todo_only=True)
72
73
74 def library_to_flowcells(request, lib_id):
75     """
76     Display information about all the flowcells a library has been run on.
77     """
78     try:
79         lib = Library.objects.get(id=lib_id)
80     except:
81         raise Http404('Library %s does not exist' % (lib_id,))
82
83     flowcell_list = []
84     flowcell_run_results = {}  # aka flowcells we're looking at
85     for lane in lib.lane_set.all():
86         fc = lane.flowcell
87         flowcell_id, id = parse_flowcell_id(fc.flowcell_id)
88         if flowcell_id not in flowcell_run_results:
89             flowcell_run_results[flowcell_id] = get_flowcell_result_dict(flowcell_id)
90         flowcell_list.append((fc.flowcell_id, lane.lane_number))
91
92     flowcell_list.sort()
93     lane_summary_list = []
94     eland_results = []
95     for fc, lane_number in flowcell_list:
96         lane_summary, err_list = _summary_stats(fc, lane_number, lib_id)
97         lane_summary_list.extend(lane_summary)
98
99         eland_results.extend(_make_eland_results(fc, lane_number, flowcell_run_results))
100
101     context = {
102         'page_name': 'Library Details',
103         'lib': lib,
104         'eland_results': eland_results,
105         'lane_summary_list': lane_summary_list,
106     }
107     context.update(SAMPLES_CONTEXT_DEFAULTS)
108
109     return render_to_response(
110         'samples/library_detail.html',
111         context,
112         context_instance=RequestContext(request))
113
114
115 def summaryhtm_fc_cnm(request, flowcell_id, cnm):
116     """
117     returns a Summary.htm file if it exists.
118     """
119     fc_id, status = parse_flowcell_id(flowcell_id)
120     d = get_flowcell_result_dict(fc_id)
121
122     if d is None:
123         return HttpResponse('<b>Results for Flowcell %s not found.</b>' % (fc_id))
124
125     if cnm not in d:
126         return HttpResponse('<b>Results for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
127
128     summary_filepath = d[cnm]['summary']
129
130     if summary_filepath is None:
131         return HttpResponse('<b>Summary.htm for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
132
133     f = open(summary_filepath, 'r')
134
135     return HttpResponse(f)
136
137
138 def result_fc_cnm_eland_lane(request, flowcell_id, cnm, lane):
139     """
140     returns an eland_file upon calling.
141     """
142     fc_id, status = parse_flowcell_id(flowcell_id)
143     d = get_flowcell_result_dict(fc_id)
144
145     if d is None:
146         return HttpResponse('<b>Results for Flowcell %s not found.</b>' % (fc_id))
147
148     if cnm not in d:
149         return HttpResponse('<b>Results for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
150
151     erd = d[cnm]['eland_results']
152     lane = int(lane)
153
154     if lane not in erd:
155         return HttpResponse('<b>Results for Flowcell %s; %s; lane %s not found.</b>' % (fc_id, cnm, lane))
156
157     filepath = erd[lane]
158
159     #f = opener.autoopen(filepath, 'r')
160     # return HttpResponse(f, content_type="application/x-elandresult")
161
162     f = open(filepath, 'r')
163     return HttpResponse(f, content_type='application/x-bzip2')
164
165
166 def bedfile_fc_cnm_eland_lane_ucsc(request, fc_id, cnm, lane):
167     """
168     returns a bed file for a given flowcell, CN-M (i.e. C1-33), and lane (ucsc compatible)
169     """
170     return bedfile_fc_cnm_eland_lane(request, fc_id, cnm, lane, ucsc_compatible=True)
171
172
173 def bedfile_fc_cnm_eland_lane(request, flowcell_id, cnm, lane, ucsc_compatible=False):
174     """
175     returns a bed file for a given flowcell, CN-M (i.e. C1-33), and lane
176     """
177     fc_id, status = parse_flowcell_id(flowcell_id)
178     d = get_flowcell_result_dict(fc_id)
179
180     if d is None:
181         return HttpResponse('<b>Results for Flowcell %s not found.</b>' % (fc_id))
182
183     if cnm not in d:
184         return HttpResponse('<b>Results for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
185
186     erd = d[cnm]['eland_results']
187     lane = int(lane)
188
189     if lane not in erd:
190         return HttpResponse('<b>Results for Flowcell %s; %s; lane %s not found.</b>' % (fc_id, cnm, lane))
191
192     filepath = erd[lane]
193
194     # Eland result file
195     fi = opener.autoopen(filepath, 'r')
196     # output memory file
197
198     name, description = makebed.make_description(fc_id, lane)
199
200     bedgen = makebed.make_bed_from_eland_generator(fi, name, description)
201
202     if ucsc_compatible:
203         return HttpResponse(bedgen)
204     else:
205         return HttpResponse(bedgen, content_type="application/x-bedfile")
206
207
208 def _summary_stats(flowcell_id, lane_id, library_id):
209     """
210     Return the summary statistics for a given flowcell, lane, and end.
211     """
212     fc_id, status = parse_flowcell_id(flowcell_id)
213     fc_result_dict = get_flowcell_result_dict(fc_id)
214
215     summary_list = []
216     err_list = []
217
218     if fc_result_dict is None:
219         err_list.append('Results for Flowcell %s not found.' % (fc_id))
220         return (summary_list, err_list)
221
222     for cycle_width in fc_result_dict:
223         xmlpath = fc_result_dict[cycle_width]['run_xml']
224
225         if xmlpath is None:
226             err_list.append('Run xml for Flowcell %s(%s) not found.' % (fc_id, cycle_width))
227             continue
228
229         run = runfolder.load_pipeline_run_xml(xmlpath)
230         # skip if we don't have available metadata.
231         if run.gerald is None or run.gerald.summary is None:
232             continue
233
234         gerald_summary = run.gerald.summary.lane_results
235         key = SampleKey(lane=lane_id, sample='s')
236         eland_results = list(run.gerald.eland_results.find_keys(key))
237         key = SampleKey(lane=lane_id, sample=library_id)
238         eland_results.extend(run.gerald.eland_results.find_keys(key))
239         for key in eland_results:
240             eland_summary = run.gerald.eland_results.results[key]
241             # add information to lane_summary
242             eland_summary.flowcell_id = flowcell_id
243
244             read = key.read-1 if key.read is not None else 0
245             try:
246                 eland_summary.clusters = gerald_summary[read][key.lane].cluster
247             except (IndexError, KeyError) as e:
248                 eland_summary.clustes = None
249             eland_summary.cycle_width = cycle_width
250             if hasattr(eland_summary, 'genome_map'):
251                 eland_summary.summarized_reads = runfolder.summarize_mapped_reads(
252                     eland_summary.genome_map,
253                     eland_summary.mapped_reads)
254
255             # grab some more information out of the flowcell db
256             flowcell = FlowCell.objects.get(flowcell_id=flowcell_id)
257             #pm_field = 'lane_%d_pM' % (lane_id)
258             lanes = flowcell.lane_set.filter(lane_number=lane_id)
259             eland_summary.flowcell = flowcell
260             eland_summary.lanes = lanes
261
262             summary_list.append(eland_summary)
263
264         #except Exception as e:
265         #    summary_list.append("Summary report needs to be updated.")
266         #    LOGGER.error("Exception: " + str(e))
267
268     return (summary_list, err_list)
269
270
271 def get_eland_result_type(pathname):
272     """
273     Guess the eland result file type from the filename
274     """
275     path, filename = os.path.split(pathname)
276     if 'extended' in filename:
277         return 'extended'
278     elif 'multi' in filename:
279         return 'multi'
280     elif 'result' in filename:
281         return 'result'
282     else:
283         return 'unknown'
284
285
286 def _make_eland_results(flowcell_id, lane_number, interesting_flowcells):
287     fc_id, status = parse_flowcell_id(flowcell_id)
288     cur_fc = interesting_flowcells.get(fc_id, None)
289     if cur_fc is None:
290         return []
291
292     flowcell = FlowCell.objects.get(flowcell_id=flowcell_id)
293     lanes = flowcell.lane_set.filter(lane_number=lane_number)
294     # Loop throw storage devices if a result has been archived
295     storage_id_list = []
296     if cur_fc is not None:
297         for lts in flowcell.longtermstorage_set.all():
298             for sd in lts.storage_devices.all():
299                 # Use barcode_id if it exists
300                 if sd.barcode_id is not None and sd.barcode_id != '':
301                     storage_id_list.append(sd.barcode_id)
302                 # Otherwise use UUID
303                 else:
304                     storage_id_list.append(sd.uuid)
305
306     # Formatting for template use
307     if len(storage_id_list) == 0:
308         storage_ids = None
309     else:
310         storage_ids = ', '.join(['<a href="/inventory/%s/">%s</a>' % (s, s) for s in storage_id_list])
311
312     results = []
313     for cycle in cur_fc.keys():
314         result_path = cur_fc[cycle]['eland_results'].get(lanes[0], None)
315         result_link = make_result_link(fc_id, cycle, lanes[0], result_path)
316         results.append({'flowcell_id': fc_id,
317                         'flowcell': flowcell,
318                         'run_date': flowcell.run_date,
319                         'cycle': cycle,
320                         'lane': lanes[0],
321                         'summary_url': make_summary_url(flowcell_id, cycle),
322                         'result_url': result_link[0],
323                         'result_label': result_link[1],
324                         'bed_url': result_link[2],
325                         'storage_ids': storage_ids})
326     return results
327
328
329 def make_summary_url(flowcell_id, cycle_name):
330     url = '/results/%s/%s/summary/' % (flowcell_id, cycle_name)
331     return url
332
333
334 def make_result_link(flowcell_id, cycle_name, lane, eland_result_path):
335     if eland_result_path is None:
336         return ("", "", "")
337
338     result_type = get_eland_result_type(eland_result_path)
339     result_url = '/results/%s/%s/eland_result/%s' % (flowcell_id, cycle_name, lane)
340     result_label = 'eland %s' % (result_type,)
341     bed_url = None
342     if result_type == 'result':
343         bed_url_pattern = '/results/%s/%s/bedfile/%s'
344         bed_url = bed_url_pattern % (flowcell_id, cycle_name, lane)
345
346     return (result_url, result_label, bed_url)
347
348
349 def _files(flowcell_id, lane):
350     """
351     Sets up available files for download
352     """
353     lane = int(lane)
354
355     flowcell_id, id = parse_flowcell_id(flowcell_id)
356     d = get_flowcell_result_dict(flowcell_id)
357
358     if d is None:
359         return ''
360
361     output = []
362
363     # c_name == 'CN-M' (i.e. C1-33)
364     for c_name in d:
365
366         if d[c_name]['summary'] is not None:
367             output.append('<a href="/results/%s/%s/summary/">summary(%s)</a>' %
368                           (flowcell_id, c_name, c_name))
369
370         erd = d[c_name]['eland_results']
371         if lane in erd:
372             result_type = get_eland_result_type(erd[lane])
373             result_url_pattern = '<a href="/results/%s/%s/eland_result/%s">eland %s(%s)</a>'
374             output.append(result_url_pattern % (flowcell_id, c_name, lane, result_type, c_name))
375             if result_type == 'result':
376                 bed_url_pattern = '<a href="/results/%s/%s/bedfile/%s">bedfile(%s)</a>'
377                 output.append(bed_url_pattern % (flowcell_id, c_name, lane, c_name))
378
379     if len(output) == 0:
380         return ''
381
382     return '(' + '|'.join(output) + ')'
383
384
385 def library_id_to_admin_url(request, lib_id):
386     lib = Library.objects.get(id=lib_id)
387     return HttpResponseRedirect('/admin/samples/library/%s' % (lib.id,))
388
389
390 def library_dict(library_id):
391     """
392     Given a library id construct a dictionary containing important information
393     return None if nothing was found
394     """
395     try:
396         lib = Library.objects.get(id=library_id)
397     except Library.DoesNotExist as e:
398         return None
399
400     #lane_info = lane_information(lib.lane_set)
401     lane_info = []
402     for lane in lib.lane_set.all():
403         lane_info.append({'flowcell': lane.flowcell.flowcell_id,
404                           'lane_number': lane.lane_number,
405                           'lane_id': lane.id,
406                           'paired_end': lane.flowcell.paired_end,
407                           'read_length': lane.flowcell.read_length,
408                           'status_code': lane.status,
409                           'status': LANE_STATUS_MAP[lane.status]})
410
411     info = {
412         # 'affiliations'?
413         # 'aligned_reads': lib.aligned_reads,
414         #'amplified_into_sample': lib.amplified_into_sample, # into is a colleciton...
415         #'amplified_from_sample_id': lib.amplified_from_sample,
416         #'antibody_name': lib.antibody_name(), # we have no antibodies.
417         'antibody_id': lib.antibody_id,
418         'cell_line_id': lib.cell_line_id,
419         'cell_line': str_or_none(lib.cell_line),
420         'experiment_type': lib.experiment_type.name,
421         'experiment_type_id': lib.experiment_type_id,
422         'gel_cut_size': lib.gel_cut_size,
423         'hidden': lib.hidden,
424         'id': lib.id,
425         'insert_size': lib.insert_size,
426         'lane_set': lane_info,
427         'library_id': lib.id,
428         'library_name': lib.library_name,
429         'library_species': lib.library_species.scientific_name,
430         'library_species_id': lib.library_species_id,
431         #'library_type': lib.library_type.name,
432         'library_type_id': lib.library_type_id,
433         'made_for': lib.made_for,
434         'made_by': lib.made_by,
435         'notes': lib.notes,
436         'replicate': lib.replicate,
437         'stopping_point': lib.stopping_point,
438         'successful_pM': str_or_none(lib.successful_pM),
439         'undiluted_concentration': str_or_none(lib.undiluted_concentration)
440         }
441     if lib.library_type_id is None:
442         info['library_type'] = None
443     else:
444         info['library_type'] = lib.library_type.name
445     return info
446
447
448 @csrf_exempt
449 def library_json(request, library_id):
450     """
451     Return a json formatted library dictionary
452     """
453     require_api_key(request)
454     # what validation should we do on library_id?
455
456     lib = library_dict(library_id)
457     if lib is None:
458         raise Http404
459
460     lib_json = json.dumps({'result': lib})
461     return HttpResponse(lib_json, content_type='application/json')
462
463
464 class LibraryViewSet(viewsets.ReadOnlyModelViewSet):
465     renderer_classes = (TemplateHTMLRenderer,
466                         BrowsableAPIRenderer,
467                         JSONRenderer)
468     queryset = Library.objects.all()
469     serializer_class = LibrarySerializer
470     pagination_class = 'rest_framework.pagination.LimitOffsetPagination'
471     default_limit = 50
472     template_name = 'samples/library_detail.html'
473
474
475 class ExperimentTypeViewSet(viewsets.ReadOnlyModelViewSet):
476     renderer_classes = (BrowsableAPIRenderer,
477                         JSONRenderer)
478     queryset = ExperimentType.objects.all()
479     serializer_class = ExperimentTypeSerializer
480
481 #@csrf_exempt
482 #def species_json(request, species_id):
483 #    """
484 #    Return information about a species.
485 #    """
486 #    raise Http404
487 #
488 #
489 #def species(request, species_id):
490 #    species = get_object_or_404(Species, id=species_id)
491 #
492 #    context = RequestContext(request,
493 #                             {'species': species})
494 #
495 #    return render_to_response("samples/species_detail.html", context)
496 #
497
498 class SpeciesViewSet(viewsets.ReadOnlyModelViewSet):
499     renderer_classes = (TemplateHTMLRenderer,
500                         BrowsableAPIRenderer,
501                         JSONRenderer)
502     queryset = Species.objects.all()
503     serializer_class = SpeciesSerializer
504     template_name = 'samples/species_detail.html'
505
506     def list(self, request, format=format):
507         queryset = Species.objects.all()
508         serializer = self.serializer_class(
509             queryset,
510             many=True,
511             context={'request': request})
512         return Response({'species': serializer.data},
513                         template_name='samples/species_list.html')
514
515
516 def antibodies(request):
517     context = RequestContext(
518         request,
519         {'antibodies': Antibody.objects.order_by('antigene')})
520     return render_to_response("samples/antibody_index.html", context)
521
522
523 @login_required
524 def user_profile(request):
525     """
526     Information about the user
527     """
528     context = {
529         'page_name': 'User Profile',
530         'media': '',
531         #'bcmagic': BarcodeMagicForm(),
532         #'select': 'settings',
533     }
534     context.update(SAMPLES_CONTEXT_DEFAULTS)
535     return render_to_response('registration/profile.html', context,
536                               context_instance=RequestContext(request))