From: Diane Trout Date: Sat, 11 Jun 2011 00:14:16 +0000 (-0700) Subject: Convert Rami's DataRun feature to something useful to us. X-Git-Tag: 0.5.2~29 X-Git-Url: http://woldlab.caltech.edu/gitweb/?p=htsworkflow.git;a=commitdiff_plain;h=e31e82da5beb1760cd59952be67a758014a27c51 Convert Rami's DataRun feature to something useful to us. (and more) The data run captures some of the core values of a run, namely the location of the result directory (relative to settings.RESULT_HOME_DIR) and what the run cycle start/stop was. The code will also scan the result directory looking for files whose names match patterns stored in the FileType table and link those to the DataRun in a DataFile table. An unresolved issue is when & how often to scan the result directory -- currently when a live site flowcell is viewed it will look in the result directory and import whatever it sees there. If it sees that result folder again it wont bother to try and import it. I might imagine problems if someone's hitting reload on a flowcell page while the result files are being copied in. I still need to implement a table to store results imported from the run xml file. Also the old result folder scanning code used in the library page should probably be updated to use the newer DataRun files. Lastly there were some layout changes to a few pages and some CSS rules got relocated. --- diff --git a/htsworkflow/frontend/experiments/admin.py b/htsworkflow/frontend/experiments/admin.py index eb243a7..4181e5c 100644 --- a/htsworkflow/frontend/experiments/admin.py +++ b/htsworkflow/frontend/experiments/admin.py @@ -1,4 +1,5 @@ -from htsworkflow.frontend.experiments.models import FlowCell, DataRun, ClusterStation, Sequencer, Lane +from htsworkflow.frontend.experiments.models import \ + FlowCell, DataRun, DataFile, FileType, ClusterStation, Sequencer, Lane from django.contrib import admin from django.contrib.admin.widgets import FilteredSelectMultiple from django.forms import ModelForm @@ -6,20 +7,45 @@ from django.forms.fields import Field, CharField from django.forms.widgets import TextInput from django.utils.translation import ugettext_lazy as _ +class DataFileForm(ModelForm): + class Meta: + model = DataFile + +class DataFileInline(admin.TabularInline): + model = DataFile + form = DataFileForm + raw_id_fields = ('library',) + extra = 0 + class DataRunOptions(admin.ModelAdmin): search_fields = [ + 'flowcell_id', 'run_folder', 'run_note', - 'config_params', ] + ] list_display = [ - 'run_folder', - 'Flowcell_Info', + 'runfolder_name', + 'result_dir', 'run_start_time', - 'main_status', - 'run_note', ] - list_filter = ('run_status', 'run_start_time') + fieldsets = ( + (None, { + 'fields': (('flowcell', 'run_status'), + ('runfolder_name', 'cycle_start', 'cycle_stop'), + ('result_dir',), + ('last_update_time'), + ('comment',)) + }), + ) + inlines = [ DataFileInline ] + #list_filter = ('run_status', 'run_start_time') +admin.site.register(DataRun, DataRunOptions) + +class FileTypeAdmin(admin.ModelAdmin): + list_display = ('name', 'mimetype', 'regex') +admin.site.register(FileType, FileTypeAdmin) + # lane form setup needs to come before Flowcell form config # as flowcell refers to the LaneInline class class LaneForm(ModelForm): @@ -63,6 +89,7 @@ class LaneOptions(admin.ModelAdmin): 'fields': ('comment', ) }), ) +admin.site.register(Lane, LaneOptions) class FlowCellOptions(admin.ModelAdmin): date_hierarchy = "run_date" @@ -92,18 +119,14 @@ class FlowCellOptions(admin.ModelAdmin): if db_field.name == "notes": field.widget.attrs["rows"] = "3" return field +admin.site.register(FlowCell, FlowCellOptions) class ClusterStationOptions(admin.ModelAdmin): list_display = ('name', ) fieldsets = ( ( None, { 'fields': ( 'name', ) } ), ) +admin.site.register(ClusterStation, ClusterStationOptions) class SequencerOptions(admin.ModelAdmin): list_display = ('name', ) fieldsets = ( ( None, { 'fields': ( 'name', ) } ), ) - - -#admin.site.register(DataRun, DataRunOptions) -admin.site.register(FlowCell, FlowCellOptions) -admin.site.register(ClusterStation, ClusterStationOptions) admin.site.register(Sequencer, SequencerOptions) -admin.site.register(Lane, LaneOptions) diff --git a/htsworkflow/frontend/experiments/fixtures/initial_data.json b/htsworkflow/frontend/experiments/fixtures/initial_data.json new file mode 100644 index 0000000..67a6393 --- /dev/null +++ b/htsworkflow/frontend/experiments/fixtures/initial_data.json @@ -0,0 +1,109 @@ +[ + { "model": "experiments.FileType", + "pk": 1, + "fields": { + "name": "run_xml", + "mimetype": "application/vnd.htsworkflow-run-xml", + "regex": "run.*\\.xml\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 2, + "fields": { + "name": "Summary.htm", + "mimetype": "text/html", + "regex": "Summary\\.htm\\Z(?ms)" + } + }, + + { "model": "experiments.FileType", + "pk": 3, + "fields": { + "name": "IVC All", + "mimetype": "image/png", + "regex": "s_(?P[0-9])_all\\.png\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 4, + "fields": { + "name": "IVC Call", + "mimetype": "image/png", + "regex": "s_(?P[0-9])_call\\.png\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 5, + "fields": { + "name": "IVC Percent All", + "mimetype": "image/png", + "regex": "s_(?P[0-9])_percent_all\\.png\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 6, + "fields": { + "name": "IVC Percent Base", + "mimetype": "image/png", + "regex": "s_(?P[0-9])_percent_base\\.png\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 7, + "fields": { + "name": "IVC Percent Call", + "mimetype": "image/png", + "regex": "s_(?P[0-9])_percent_call\\.png\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 8, + "fields": { + "name": "GERALD Scores", + "regex": "scores\\.tar\\.bz2\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 9, + "fields": { + "name": "ELAND Result", + "regex": "s_(?P[0-9])((?P[1-4])_)?_eland_result\\.txt\\.bz2\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 10, + "fields": { + "name": "ELAND Multi", + "regex": "s_(?P[0-9])((?P[1-4])_)?_eland_multi\\.txt\\.bz2\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 11, + "fields": { + "name": "ELAND Extended", + "regex": "s_(?P[0-9])((?P[1-4])_)?_eland_extended\\.txt\\.bz2\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 12, + "fields": { + "name": "ELAND Export", + "regex": "s_(?P[0-9])((?P[1-4])_)?_export\\.txt\\.bz2\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 13, + "fields": { + "name": "SRF", + "regex": ".*_(?P[1-8])\\.srf\\Z(?ms)" + } + }, + { "model": "experiments.FileType", + "pk": 14, + "fields": { + "name": "QSEQ tarfile", + "regex": ".*_l(?P[1-8])_r(?P[1-4])\\.tar\\.bz2\\Z(?ms)" + } + } + +] \ No newline at end of file diff --git a/htsworkflow/frontend/experiments/fixtures/test_flowcells.json b/htsworkflow/frontend/experiments/fixtures/test_flowcells.json index 37adf5e..a37fced 100644 --- a/htsworkflow/frontend/experiments/fixtures/test_flowcells.json +++ b/htsworkflow/frontend/experiments/fixtures/test_flowcells.json @@ -105,14 +105,14 @@ {"pk": 153, "model": "experiments.flowcell", "fields": { "paired_end": true, - "run_date": "2009-09-11 22:12:13", - "read_length": 75, + "run_date": "2007-09-27 22:12:13", + "read_length": 36, "notes": "", "advanced_run": false, "control_lane": 2, "cluster_station": 3, "sequencer": 2, - "flowcell_id": "303TUAAXX" + "flowcell_id": "FC12150" } }, {"pk": 1193, "model": "experiments.lane", diff --git a/htsworkflow/frontend/experiments/models.py b/htsworkflow/frontend/experiments/models.py index 8a71e25..4e060f9 100755 --- a/htsworkflow/frontend/experiments/models.py +++ b/htsworkflow/frontend/experiments/models.py @@ -1,12 +1,39 @@ +import datetime +import glob import logging +import os +import re +import types +import uuid +from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core import urlresolvers from django.db import models +from django.db.models.signals import post_init -from htsworkflow.frontend.samples.models import * -#from htsworkflow.frontend.settings import options -from django.conf import settings +from htsworkflow.frontend.samples.models import Library +from htsworkflow.frontend.samples.results import parse_flowcell_id +from htsworkflow.pipelines import runfolder + +default_pM = 5 +try: + default_pM = int(settings.DEFAULT_PM) +except ValueError,e: + logging.error("invalid value for frontend.default_pm") + +RUN_STATUS_CHOICES = ( + (0, 'Sequencer running'), ##Solexa Data Pipeline Not Yet Started'), + (1, 'Data Pipeline Started'), + (2, 'Data Pipeline Interrupted'), + (3, 'Data Pipeline Finished'), + (4, 'Collect Results Started'), + (5, 'Collect Results Finished'), + (6, 'QC Started'), + (7, 'QC Finished'), + (255, 'DONE'), + ) +RUN_STATUS_REVERSE_MAP = dict(((v,k) for k,v in RUN_STATUS_CHOICES)) class ClusterStation(models.Model): name = models.CharField(max_length=50, unique=True) @@ -20,20 +47,13 @@ class Sequencer(models.Model): def __unicode__(self): return unicode(self.name) -default_pM = 5 -try: - default_pM = int(settings.DEFAULT_PM) -except ValueError,e: - logging.error("invalid value for frontend.default_pm") - class FlowCell(models.Model): - flowcell_id = models.CharField(max_length=20, unique=True, db_index=True) run_date = models.DateTimeField() advanced_run = models.BooleanField(default=False) paired_end = models.BooleanField(default=False) read_length = models.IntegerField(default=32) #Stanford is currenlty 25 - control_lane = models.IntegerField(choices=[(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(0,'All Lanes')], null=True) + control_lane = models.IntegerField(choices=[(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(0,'All Lanes')], null=True, blank=True) cluster_station = models.ForeignKey(ClusterStation, default=3) sequencer = models.ForeignKey(Sequencer, default=1) @@ -43,21 +63,8 @@ class FlowCell(models.Model): def __unicode__(self): return unicode(self.flowcell_id) - def Create_LOG(self): - str = '' - str +='Create LOG' - try: - t = DataRun.objects.get(fcid=self.id) - str +='
DataRun ..' - except ObjectDoesNotExist: - str += '
not sequenced' - return str - Create_LOG.allow_tags = True - def Lanes(self): - library_url = '/admin/samples/library/%s' html = [''] - #for i in range(1,9): for lane in self.lane_set.all(): cluster_estimate = lane.cluster_estimate if cluster_estimate is not None: @@ -67,8 +74,10 @@ class FlowCell(models.Model): library_id = lane.library_id library = lane.library element = '' - expanded_library_url = library_url %(library_id,) - html.append(element % (lane.lane_number, expanded_library_url, library, cluster_estimate)) + html.append(element % (lane.lane_number, + library.get_admin_url(), + library, + cluster_estimate)) html.append('
%d%s%s
') return "\n".join(html) Lanes.allow_tags = True @@ -78,8 +87,8 @@ class FlowCell(models.Model): def get_admin_url(self): # that's the django way... except it didn't work - #return urlresolvers.reverse('admin_experiments_FlowCell_change', args=(self.id,)) - return '/admin/experiments/flowcell/%s/' % (self.id,) + return urlresolvers.reverse('admin:experiments_flowcell_change', + args=(self.id,)) def flowcell_type(self): """ @@ -95,59 +104,63 @@ class FlowCell(models.Model): return ('htsworkflow.frontend.experiments.views.flowcell_detail', [str(self.flowcell_id)]) -### ----------------------- -class DataRun(models.Model): - ConfTemplate = "CONFIG PARAMS WILL BE GENERATED BY THE PIPELINE SCRIPT.\nYOU'LL BE ABLE TO EDIT AFTER IF NEEDED." - run_folder = models.CharField(max_length=50,unique=True, db_index=True) - fcid = models.ForeignKey(FlowCell,verbose_name="Flowcell Id") - config_params = models.TextField(default=ConfTemplate) - run_start_time = models.DateTimeField() - RUN_STATUS_CHOICES = ( - (0, 'Sequencer running'), ##Solexa Data Pipeline Not Yet Started'), - (1, 'Data Pipeline Started'), - (2, 'Data Pipeline Interrupted'), - (3, 'Data Pipeline Finished'), - (4, 'CollectReads Started'), - (5, 'CollectReads Finished'), - (6, 'QC Finished'), - (7, 'DONE'), - ) - run_status = models.IntegerField(choices=RUN_STATUS_CHOICES, default=0) - run_note = models.TextField(blank=True) - - - def main_status(self): - str = '= 5: - str += ' style="color:green">' - str += ''+self.RUN_STATUS_CHOICES[self.run_status][1]+'' - str += '

' #New!' - str +='
View QC Page' - else: - str += '>'+self.RUN_STATUS_CHOICES[self.run_status][1] + def get_raw_data_directory(self): + """Return location of where the raw data is stored""" + flowcell_id, status = parse_flowcell_id(self.flowcell_id) - str += '' - return str - main_status.allow_tags = True + return os.path.join(settings.RESULT_HOME_DIR, flowcell_id) - main_status.allow_tags = True + def update_data_runs(self): + result_root = self.get_raw_data_directory() + if result_root is None: + return - def Flowcell_Info(self): - str = ''+self.fcid.__str__()+'' - str += ' (c: '+self.fcid.cluster_mac_id+', s: '+self.fcid.seq_mac_id+')' - str += '
' - str +='View/hide lanes' - str += '
' - LanesList = '1: '+self.fcid.lane_1_library.__str__()+' ('+self.fcid.lane_1_library.library_species.use_genome_build+')
2: '+self.fcid.lane_2_library.__str__()+' ('+self.fcid.lane_2_library.library_species.use_genome_build+')
3: '+self.fcid.lane_3_library.__str__()+' ('+self.fcid.lane_3_library.library_species.use_genome_build+')
4: '+self.fcid.lane_4_library.__str__()+' ('+self.fcid.lane_4_library.library_species.use_genome_build+')
5: '+self.fcid.lane_5_library.__str__()+' ('+self.fcid.lane_5_library.library_species.use_genome_build+')
6: '+self.fcid.lane_6_library.__str__()+' ('+self.fcid.lane_6_library.library_species.use_genome_build+')
7: '+self.fcid.lane_7_library.__str__()+' ('+self.fcid.lane_7_library.library_species.use_genome_build+')
8: '+self.fcid.lane_8_library.__str__()+' ('+self.fcid.lane_8_library.library_species.use_genome_build+')' - str += LanesList ## self.fcid.Lanes() - str += '
' - str += '
Edit Flowcell record' - #str += 'New!' - str +='GA LOG Page' - str += '
' - str += '
' - return str - Flowcell_Info.allow_tags = True + result_home_dir = os.path.join(settings.RESULT_HOME_DIR,'') + run_xml_re = re.compile(glob.fnmatch.translate('run*.xml')) + + dataruns = self.datarun_set.all() + datarun_result_dirs = [ x.result_dir for x in dataruns ] + + result_dirs = [] + for dirpath, dirnames, filenames in os.walk(result_root): + for filename in filenames: + if run_xml_re.match(filename): + # we have a run directory + relative_pathname = get_relative_pathname(dirpath) + if relative_pathname not in datarun_result_dirs: + self.import_data_run(relative_pathname, filename) + + def import_data_run(self, relative_pathname, run_xml_name): + """Given a result directory import files""" + run_dir = get_absolute_pathname(relative_pathname) + run_xml_path = os.path.join(run_dir, run_xml_name) + run_xml_data = runfolder.load_pipeline_run_xml(run_xml_path) + + run = DataRun() + run.flowcell = self + run.status = RUN_STATUS_REVERSE_MAP['DONE'] + run.result_dir = relative_pathname + run.runfolder_name = run_xml_data.runfolder_name + run.cycle_start = run_xml_data.image_analysis.start + run.cycle_stop = run_xml_data.image_analysis.stop + run.run_start_time = run_xml_data.image_analysis.date + run.last_update_time = datetime.datetime.now() + run.save() + + run.update_result_files() + +# FIXME: should we automatically update dataruns? +# Or should we expect someone to call update_data_runs? +#def update_flowcell_dataruns(sender, instance, *args, **kwargs): +# """Update our dataruns +# """ +# if not os.path.exists(settings.RESULT_HOME_DIR): +# return +# +# instance.update_data_runs() +#post_init.connect(update_flowcell_dataruns, sender=FlowCell) + + LANE_STATUS_CODES = [(0, 'Failed'), (1, 'Marginal'), @@ -168,3 +181,156 @@ class Lane(models.Model): def get_absolute_url(self): return ('htsworkflow.frontend.experiments.views.flowcell_lane_detail', [str(self.flowcell.flowcell_id), str(self.lane_number)]) + + +### ----------------------- +class DataRun(models.Model): + flowcell = models.ForeignKey(FlowCell,verbose_name="Flowcell Id") + runfolder_name = models.CharField(max_length=50) + result_dir = models.CharField(max_length=255) + last_update_time = models.DateTimeField() + run_start_time = models.DateTimeField() + cycle_start = models.IntegerField(null=True, blank=True) + cycle_stop = models.IntegerField(null=True, blank=True) + run_status = models.IntegerField(choices=RUN_STATUS_CHOICES, + null=True, blank=True) + comment = models.TextField(blank=True) + + def update_result_files(self): + abs_result_dir = get_absolute_pathname(self.result_dir) + + for dirname, dirnames, filenames in os.walk(abs_result_dir): + for filename in filenames: + pathname = os.path.join(dirname, filename) + relative_pathname = get_relative_pathname(pathname) + datafiles = self.datafile_set.filter( + data_run = self, + relative_pathname=relative_pathname) + if len(datafiles) > 0: + continue + + metadata = find_file_type_metadata_from_filename(filename) + if metadata is not None: + metadata['filename'] = filename + newfile = DataFile() + newfile.data_run = self + newfile.file_type = metadata['file_type'] + newfile.relative_pathname = relative_pathname + + lane_number = metadata.get('lane', None) + if lane_number is not None: + lane = self.flowcell.lane_set.get(lane_number = lane_number) + newfile.library = lane.library + + self.datafile_set.add(newfile) + + self.last_update_time = datetime.datetime.now() + + def lane_files(self): + lanes = {} + + for datafile in self.datafile_set.all(): + metadata = datafile.attributes + if metadata is not None: + lane = metadata.get('lane', None) + if lane is not None: + lane_file_set = lanes.setdefault(lane, {}) + lane_file_set[datafile.file_type.normalized_name] = datafile + return lanes + + def ivc_plots(self, lane): + ivc_name = ['IVC All', 'IVC Call', + 'IVC Percent Base', 'IVC Percent All', 'IVC Percent Call'] + + plots = {} + for rel_filename, metadata in self.get_result_files(): + if metadata.file_type.name in ivc_name: + plots[metadata.file_type.name] = (rel_filename, metadata) + +class FileType(models.Model): + """Represent potential file types + + regex is a pattern used to detect if a filename matches this type + data run currently assumes that there may be a (?P) and + (?P) pattern in the regular expression. + """ + name = models.CharField(max_length=50) + mimetype = models.CharField(max_length=50, null=True, blank=True) + # regular expression from glob.fnmatch.translate + regex = models.CharField(max_length=50, null=True, blank=True) + + def parse_filename(self, pathname): + """Does filename match our pattern? + + Returns None if not, or dictionary of match variables if we do. + """ + path, filename = os.path.split(pathname) + if len(self.regex) > 0: + match = re.match(self.regex, filename) + if match is not None: + # These are (?P<>) names we know about from our default regexes. + results = match.groupdict() + + # convert int parameters + for attribute_name in ['lane', 'end']: + value = results.get(attribute_name, None) + if value is not None: + results[attribute_name] = int(value) + + return results + + def _get_normalized_name(self): + """Crush data file name into identifier friendly name""" + return self.name.replace(' ', '_').lower() + normalized_name = property(_get_normalized_name) + + def __unicode__(self): + #return u"" % (self.name,) + return self.name + + +class DataFile(models.Model): + """Store map from random ID to filename""" + random_key = models.CharField(max_length=16, + db_index=True, + default=uuid.uuid1) + data_run = models.ForeignKey(DataRun, db_index=True) + library = models.ForeignKey(Library, db_index=True, null=True, blank=True) + file_type = models.ForeignKey(FileType) + relative_pathname = models.CharField(max_length=255, db_index=True) + + def _get_attributes(self): + return self.file_type.parse_filename(self.relative_pathname) + attributes = property(_get_attributes) + + def _get_pathname(self): + return get_absolute_pathname(self.relative_pathname) + pathname = property(_get_pathname) + + @models.permalink + def get_absolute_url(self): + return ('htsworkflow.frontend.experiments.views.read_result_file', + (), {'key': self.random_key }) + +def find_file_type_metadata_from_filename(pathname): + path, filename = os.path.split(pathname) + result = None + for file_type in FileType.objects.all(): + result = file_type.parse_filename(filename) + if result is not None: + result['file_type'] = file_type + return result + + return None + +def get_relative_pathname(abspath): + """Strip off the result home directory from a path + """ + result_home_dir = os.path.join(settings.RESULT_HOME_DIR,'') + relative_pathname = abspath.replace(result_home_dir,'') + return relative_pathname + +def get_absolute_pathname(relative_pathname): + """Attach relative path to results home directory""" + return os.path.join(settings.RESULT_HOME_DIR, relative_pathname) + diff --git a/htsworkflow/frontend/experiments/tests.py b/htsworkflow/frontend/experiments/tests.py index 7bfebfe..16e31ee 100644 --- a/htsworkflow/frontend/experiments/tests.py +++ b/htsworkflow/frontend/experiments/tests.py @@ -5,8 +5,11 @@ try: except ImportError, e: import simplejson as json import os +import shutil import sys +import tempfile +from django.conf import settings from django.core import mail from django.core.exceptions import ObjectDoesNotExist from django.test import TestCase @@ -14,19 +17,47 @@ from htsworkflow.frontend.experiments import models from htsworkflow.frontend.experiments import experiments from htsworkflow.frontend.auth import apidata +from htsworkflow.pipelines.test.simulate_runfolder import TESTDATA_DIR + LANE_SET = range(1,9) class ExperimentsTestCases(TestCase): fixtures = ['test_flowcells.json'] def setUp(self): - pass + self.tempdir = tempfile.mkdtemp(prefix='htsw-test-experiments-') + settings.RESULT_HOME_DIR = self.tempdir + + self.fc1_id = 'FC12150' + self.fc1_root = os.path.join(self.tempdir, self.fc1_id) + os.mkdir(self.fc1_root) + self.fc1_dir = os.path.join(self.fc1_root, 'C1-37') + os.mkdir(self.fc1_dir) + runxml = 'run_FC12150_2007-09-27.xml' + shutil.copy(os.path.join(TESTDATA_DIR, runxml), + os.path.join(self.fc1_dir, runxml)) + for i in range(1,9): + shutil.copy( + os.path.join(TESTDATA_DIR, + 'woldlab_070829_USI-EAS44_0017_FC11055_1.srf'), + os.path.join(self.fc1_dir, + 'woldlab_070829_SERIAL_FC12150_%d.srf' %(i,)) + ) + + self.fc2_dir = os.path.join(self.tempdir, '42JTNAAXX') + os.mkdir(self.fc2_dir) + os.mkdir(os.path.join(self.fc2_dir, 'C1-25')) + os.mkdir(os.path.join(self.fc2_dir, 'C1-37')) + os.mkdir(os.path.join(self.fc2_dir, 'C1-37', 'Plots')) + + def tearDown(self): + shutil.rmtree(self.tempdir) def test_flowcell_information(self): """ Check the code that packs the django objects into simple types. """ - for fc_id in [u'303TUAAXX', u"42JTNAAXX", "42JU1AAXX"]: + for fc_id in [u'FC12150', u"42JTNAAXX", "42JU1AAXX"]: fc_dict = experiments.flowcell_information(fc_id) fc_django = models.FlowCell.objects.get(flowcell_id=fc_id) self.failUnlessEqual(fc_dict['flowcell_id'], fc_id) @@ -81,14 +112,14 @@ class ExperimentsTestCases(TestCase): """ Require logging in to retrieve meta data """ - response = self.client.get(u'/experiments/config/303TUAAXX/json') + response = self.client.get(u'/experiments/config/FC12150/json') self.failUnlessEqual(response.status_code, 403) def test_library_id(self): """ Library IDs should be flexible, so make sure we can retrive a non-numeric ID """ - response = self.client.get('/experiments/config/303TUAAXX/json', apidata) + response = self.client.get('/experiments/config/FC12150/json', apidata) self.failUnlessEqual(response.status_code, 200) flowcell = json.loads(response.content) @@ -165,6 +196,128 @@ class ExperimentsTestCases(TestCase): self.failUnlessEqual(response.status_code, 404) + def test_raw_data_dir(self): + """Raw data path generator check""" + flowcell_id = self.fc1_id + raw_dir = os.path.join(settings.RESULT_HOME_DIR, flowcell_id) + + fc = models.FlowCell.objects.get(flowcell_id=flowcell_id) + self.failUnlessEqual(fc.get_raw_data_directory(), raw_dir) + + fc.flowcell_id = flowcell_id + " (failed)" + self.failUnlessEqual(fc.get_raw_data_directory(), raw_dir) + + + def test_data_run_import(self): + srf_file_type = models.FileType.objects.get(name='SRF') + runxml_file_type = models.FileType.objects.get(name='run_xml') + flowcell_id = self.fc1_id + flowcell = models.FlowCell.objects.get(flowcell_id=flowcell_id) + flowcell.update_data_runs() + self.failUnlessEqual(len(flowcell.datarun_set.all()), 1) + + run = flowcell.datarun_set.all()[0] + result_files = run.datafile_set.all() + result_dict = dict(((rf.relative_pathname, rf) for rf in result_files)) + + srf4 = result_dict['FC12150/C1-37/woldlab_070829_SERIAL_FC12150_4.srf'] + self.failUnlessEqual(srf4.file_type, srf_file_type) + self.failUnlessEqual(srf4.library_id, '11060') + self.failUnlessEqual(srf4.data_run.flowcell.flowcell_id, 'FC12150') + self.failUnlessEqual( + srf4.data_run.flowcell.lane_set.get(lane_number=4).library_id, + '11060') + self.failUnlessEqual( + srf4.pathname, + os.path.join(settings.RESULT_HOME_DIR, srf4.relative_pathname)) + + lane_files = run.lane_files() + self.failUnlessEqual(lane_files[4]['srf'], srf4) + + runxml= result_dict['FC12150/C1-37/run_FC12150_2007-09-27.xml'] + self.failUnlessEqual(runxml.file_type, runxml_file_type) + self.failUnlessEqual(runxml.library_id, None) + + + def test_read_result_file(self): + """make sure we can return a result file + """ + flowcell_id = self.fc1_id + flowcell = models.FlowCell.objects.get(flowcell_id=flowcell_id) + flowcell.update_data_runs() + + #self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5') + + result_files = flowcell.datarun_set.all()[0].datafile_set.all() + for f in result_files: + url = '/experiments/file/%s' % ( f.random_key,) + response = self.client.get(url) + self.failUnlessEqual(response.status_code, 200) + mimetype = f.file_type.mimetype + if mimetype is None: + mimetype = 'application/octet-stream' + + self.failUnlessEqual(mimetype, response['content-type']) + +class TestFileType(TestCase): + def test_file_type_unicode(self): + file_type_objects = models.FileType.objects + name = 'QSEQ tarfile' + file_type_object = file_type_objects.get(name=name) + self.failUnlessEqual(u"", + unicode(file_type_object)) + +class TestFileType(TestCase): + def test_find_file_type(self): + file_type_objects = models.FileType.objects + cases = [('woldlab_090921_HWUSI-EAS627_0009_42FC3AAXX_l7_r1.tar.bz2', + 'QSEQ tarfile', 7, 1), + ('woldlab_091005_HWUSI-EAS627_0010_42JT2AAXX_1.srf', + 'SRF', 1, None), + ('s_1_eland_extended.txt.bz2','ELAND Extended', 1, None), + ('s_7_eland_multi.txt.bz2', 'ELAND Multi', 7, None), + ('s_3_eland_result.txt.bz2','ELAND Result', 3, None), + ('s_1_export.txt.bz2','ELAND Export', 1, None), + ('s_1_percent_call.png', 'IVC Percent Call', 1, None), + ('s_2_percent_base.png', 'IVC Percent Base', 2, None), + ('s_3_percent_all.png', 'IVC Percent All', 3, None), + ('s_4_call.png', 'IVC Call', 4, None), + ('s_5_all.png', 'IVC All', 5, None), + ('Summary.htm', 'Summary.htm', None, None), + ('run_42JT2AAXX_2009-10-07.xml', 'run_xml', None, None), + ] + for filename, typename, lane, end in cases: + ft = models.find_file_type_metadata_from_filename(filename) + self.failUnlessEqual(ft['file_type'], + file_type_objects.get(name=typename)) + self.failUnlessEqual(ft.get('lane', None), lane) + self.failUnlessEqual(ft.get('end', None), end) + + def test_assign_file_type_complex_path(self): + file_type_objects = models.FileType.objects + cases = [('/a/b/c/woldlab_090921_HWUSI-EAS627_0009_42FC3AAXX_l7_r1.tar.bz2', + 'QSEQ tarfile', 7, 1), + ('foo/woldlab_091005_HWUSI-EAS627_0010_42JT2AAXX_1.srf', + 'SRF', 1, None), + ('../s_1_eland_extended.txt.bz2','ELAND Extended', 1, None), + ('/bleem/s_7_eland_multi.txt.bz2', 'ELAND Multi', 7, None), + ('/qwer/s_3_eland_result.txt.bz2','ELAND Result', 3, None), + ('/ty///1/s_1_export.txt.bz2','ELAND Export', 1, None), + ('/help/s_1_percent_call.png', 'IVC Percent Call', 1, None), + ('/bored/s_2_percent_base.png', 'IVC Percent Base', 2, None), + ('/example1/s_3_percent_all.png', 'IVC Percent All', 3, None), + ('amonkey/s_4_call.png', 'IVC Call', 4, None), + ('fishie/s_5_all.png', 'IVC All', 5, None), + ('/random/Summary.htm', 'Summary.htm', None, None), + ('/notrandom/run_42JT2AAXX_2009-10-07.xml', 'run_xml', None, None), + ] + for filename, typename, lane, end in cases: + result = models.find_file_type_metadata_from_filename(filename) + self.failUnlessEqual(result['file_type'], + file_type_objects.get(name=typename)) + self.failUnlessEqual(result.get('lane',None), lane) + self.failUnlessEqual(result.get('end', None), end) + class TestEmailNotify(TestCase): fixtures = ['test_flowcells.json'] @@ -203,7 +356,7 @@ class TestEmailNotify(TestCase): self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5') response = self.client.get('/experiments/started/153/') self.failUnlessEqual(response.status_code, 200) - self.failUnless(re.search('Flowcell 303TUAAXX', response.content)) + self.failUnless(re.search('Flowcell FC12150', response.content)) # require that navigation back to the admin page exists self.failUnless(re.search('[^<]+', response.content)) diff --git a/htsworkflow/frontend/experiments/urls.py b/htsworkflow/frontend/experiments/urls.py index 5119e9d..91202a8 100755 --- a/htsworkflow/frontend/experiments/urls.py +++ b/htsworkflow/frontend/experiments/urls.py @@ -6,14 +6,8 @@ urlpatterns = patterns('', #(r'^(?P.+)/$', 'gaworkflow.frontend.experiments.views.detail'), (r'^config/(?P.+)/json$', 'htsworkflow.frontend.experiments.experiments.flowcell_json'), (r'^lanes_for/(?P.+)/json$', 'htsworkflow.frontend.experiments.experiments.lanes_for_json'), - (r'^fcsheet/(?P.+)/$', 'htsworkflow.frontend.experiments.views.makeFCSheet'), - (r'^updStatus$', 'htsworkflow.frontend.experiments.experiments.updStatus'), - (r'^getConfile$', 'htsworkflow.frontend.experiments.experiments.getConfile'), - (r'^getLanesNames$', 'htsworkflow.frontend.experiments.experiments.getLaneLibs'), - # for the following two URLS I have to pass in the primary key - # because I link to the page from an overridden version of the admin change_form - # which only makes the object primary key available in the form. - # (Or at least as far as I could tell) + (r'^file/(?P.+)/?$', 'htsworkflow.frontend.experiments.views.read_result_file'), (r'^started/(?P.+)/$', 'htsworkflow.frontend.experiments.views.startedEmail'), (r'^finished/(?P.+)/$', 'htsworkflow.frontend.experiments.views.finishedEmail'), + ) diff --git a/htsworkflow/frontend/experiments/views.py b/htsworkflow/frontend/experiments/views.py index 342438d..827f521 100755 --- a/htsworkflow/frontend/experiments/views.py +++ b/htsworkflow/frontend/experiments/views.py @@ -1,9 +1,11 @@ # Create your views here. from datetime import datetime +import os #from django.template import Context, loader #shortcut to the above modules from django.contrib.auth.decorators import user_passes_test +from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.mail import EmailMessage, mail_managers from django.http import HttpResponse @@ -11,7 +13,7 @@ from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext from django.template.loader import get_template -from htsworkflow.frontend.experiments.models import * +from htsworkflow.frontend.experiments.models import DataRun, DataFile, FlowCell from htsworkflow.frontend.experiments.experiments import \ estimateFlowcellDuration, \ estimateFlowcellTimeRemaining, \ @@ -129,6 +131,7 @@ def finishedEmail(request, pk): def flowcell_detail(request, flowcell_id): fc = get_object_or_404(FlowCell, flowcell_id=flowcell_id) + fc.update_data_runs() context = RequestContext(request, {'flowcell': fc}) @@ -139,6 +142,8 @@ def flowcell_detail(request, flowcell_id): def flowcell_lane_detail(request, flowcell_id, lane_number): fc = get_object_or_404(FlowCell, flowcell_id=flowcell_id) lane = get_object_or_404(fc.lane_set, lane_number=lane_number) + + fc.update_data_runs() context = RequestContext(request, {'lib': lane.library, @@ -147,3 +152,20 @@ def flowcell_lane_detail(request, flowcell_id, lane_number): return render_to_response('experiments/flowcell_lane_detail.html', context) + +def read_result_file(self, key): + """Return the contents of filename if everything is approved + """ + data_file = get_object_or_404(DataFile, random_key = key) + + mimetype = 'application/octet-stream' + if data_file.file_type.mimetype is not None: + mimetype = data_file.file_type.mimetype + + if os.path.exists(data_file.pathname): + return HttpResponse(open(data_file.pathname,'r'), + mimetype=mimetype) + + raise Http404 + + diff --git a/htsworkflow/frontend/samples/models.py b/htsworkflow/frontend/samples/models.py index e49d23b..838bea6 100644 --- a/htsworkflow/frontend/samples/models.py +++ b/htsworkflow/frontend/samples/models.py @@ -2,12 +2,11 @@ import logging import urlparse from django.db import models from django.contrib.auth.models import User, UserManager +from django.core import urlresolvers from django.db.models.signals import pre_save, post_save from django.db import connection from htsworkflow.frontend.reports.libinfopar import * - -# Create your models here. logger = logging.getLogger(__name__) class Antibody(models.Model): @@ -289,6 +288,9 @@ class Library(models.Model): def get_absolute_url(self): return ('htsworkflow.frontend.samples.views.library_to_flowcells', [str(self.id)]) + def get_admin_url(self): + return urlresolvers.reverse('admin:samples_library_change', + args=(self.id,)) class HTSUser(User): """ diff --git a/htsworkflow/frontend/samples/views.py b/htsworkflow/frontend/samples/views.py index 2bbcc89..c48826c 100644 --- a/htsworkflow/frontend/samples/views.py +++ b/htsworkflow/frontend/samples/views.py @@ -128,9 +128,9 @@ def library_to_flowcells(request, lib_id): eland_results = [] for fc, lane_number in flowcell_list: lane_summary, err_list = _summary_stats(fc, lane_number) - - eland_results.extend(_make_eland_results(fc, lane_number, flowcell_run_results)) lane_summary_list.extend(lane_summary) + + eland_results.extend(_make_eland_results(fc, lane_number, flowcell_run_results)) context = { 'page_name': 'Library Details', @@ -308,7 +308,8 @@ def _summary_stats(flowcell_id, lane_id): flowcell = FlowCell.objects.get(flowcell_id=flowcell_id) #pm_field = 'lane_%d_pM' % (lane_id) lane_obj = flowcell.lane_set.get(lane_number=lane_id) - eland_summary.successful_pm = lane_obj.pM + eland_summary.flowcell = flowcell + eland_summary.lane = lane_obj summary_list.append(eland_summary) @@ -318,65 +319,6 @@ def _summary_stats(flowcell_id, lane_id): return (summary_list, err_list) -def _summary_stats_old(flowcell_id, lane): - """ - return a dictionary of summary stats for a given flowcell_id & lane. - """ - fc_id, status = parse_flowcell_id(flowcell_id) - fc_result_dict = get_flowcell_result_dict(fc_id) - - dict_list = [] - err_list = [] - summary_list = [] - - if fc_result_dict is None: - err_list.append('Results for Flowcell %s not found.' % (fc_id)) - return (dict_list, err_list, summary_list) - - for cnm in fc_result_dict: - - xmlpath = fc_result_dict[cnm]['run_xml'] - - if xmlpath is None: - err_list.append('Run xml for Flowcell %s(%s) not found.' % (fc_id, cnm)) - continue - - tree = ElementTree.parse(xmlpath).getroot() - results = runfolder.PipelineRun(pathname='', xml=tree) - try: - lane_report = runfolder.summarize_lane(results.gerald, lane) - summary_list.append(os.linesep.join(lane_report)) - except Exception, e: - summary_list.append("Summary report needs to be updated.") - logging.error("Exception: " + str(e)) - - print >>sys.stderr, "----------------------------------" - print >>sys.stderr, "-- DOES NOT SUPPORT PAIRED END ---" - print >>sys.stderr, "----------------------------------" - lane_results = results.gerald.summary[0][lane] - lrs = lane_results - - d = {} - - d['average_alignment_score'] = lrs.average_alignment_score - d['average_first_cycle_intensity'] = lrs.average_first_cycle_intensity - d['cluster'] = lrs.cluster - d['lane'] = lrs.lane - d['flowcell'] = flowcell_id - d['cnm'] = cnm - d['percent_error_rate'] = lrs.percent_error_rate - d['percent_intensity_after_20_cycles'] = lrs.percent_intensity_after_20_cycles - d['percent_pass_filter_align'] = lrs.percent_pass_filter_align - d['percent_pass_filter_clusters'] = lrs.percent_pass_filter_clusters - - #FIXME: function finished, but need to take advantage of - # may need to take in a list of lanes so we only have to - # load the xml file once per flowcell rather than once - # per lane. - dict_list.append(d) - - return (dict_list, err_list, summary_list) - def get_eland_result_type(pathname): """ diff --git a/htsworkflow/frontend/static/css/app.css b/htsworkflow/frontend/static/css/app.css index fec1733..0a86f8a 100644 --- a/htsworkflow/frontend/static/css/app.css +++ b/htsworkflow/frontend/static/css/app.css @@ -66,20 +66,76 @@ div.htswdetail h3 { margin: 0; } - div.htswdetail h4, - div.htswdetail h5, - div.htswdetail ul, - div.htswdetail ol, - div.htswdetail li - { - list-style: none; - margin: 0; - } +div.htswdetail h4, +div.htswdetail h5, +div.htswdetail ul, +div.htswdetail ol, +div.htswdetail li +{ + list-style: none; + margin: 0; +} + +div.htswdetail ul, +div.htswdetail ol +{ + margin-bottom: .5em; +} - div.htswdetail ul, - div.htswdetail ol - { - margin-bottom: .5em; +/* style library detail headers */ +div#librarydetail { + margin: 0; + padding: 0; +} +div#librarydetail table, div#librarydetail td { + border-style: solid; +} +div#librarydetail table { + border-width: 0 0 1px 1px; + border-spacing: 0; + border-collapse: collapse; +} +div#librarydetail td { + margin: 0; + padding: 3px; + border-width: 1px 1px 0 0; +} +div#librarydetail thead { + text-align: center; } - /* ]]> */ - +div#librarydetail tbody { + text-align: right; +} +div#librarydetail h1, +div#librarydetail h2 +{ + font-size: 150%; +} + +div#librarydetail h3 { + font-size: 125%; + margin: 0; +} + +div#librarydetail h4, +div#librarydetail h5, +div#librarydetail ul, +div#librarydetail ol, +div#librarydetail li +{ + list-style: none; + margin: 0; +} + +div#librarydetail ul, +div#librarydetail ol +{ + margin-bottom: .5em; +} + +div.library_identity { + float: left; margin: 5px; } +div.library_sample_detail { float: left; margin: 5px; } +div.library_library_detail { float: left; margin: 5px; } +div.library_statistics { clear: both; border: 1px; } + diff --git a/htsworkflow/frontend/templates/experiments/flowcell_detail.html b/htsworkflow/frontend/templates/experiments/flowcell_detail.html index 3c9329c..b238f8b 100644 --- a/htsworkflow/frontend/templates/experiments/flowcell_detail.html +++ b/htsworkflow/frontend/templates/experiments/flowcell_detail.html @@ -24,7 +24,7 @@ {{flowcell.control_lane}}
Notes: -

{{flowcell.notes}}

+
{{flowcell.notes}}

Lanes

@@ -54,5 +54,52 @@
+
+ {% for run in flowcell.datarun_set.all %} +

Run {{ run.runfolder_name }}

+ + + + + + + + + + + + {% for lane_id, lane_file_set in run.lane_files.items %} + + + + + + + + + {% endfor %} + +
LaneIVC AllIVC CallIVC Percent BaseIVC Percent Base AllIVC Percent Base Called
{{ lane_id }} + + + + + + + + + + + + + + + + + + +
+ {% endfor %} +
{% endblock %} diff --git a/htsworkflow/frontend/templates/experiments/flowcell_lane_detail.html b/htsworkflow/frontend/templates/experiments/flowcell_lane_detail.html index aceb186..f65c9e9 100644 --- a/htsworkflow/frontend/templates/experiments/flowcell_lane_detail.html +++ b/htsworkflow/frontend/templates/experiments/flowcell_lane_detail.html @@ -10,7 +10,7 @@ {% endblock %} {% block content %} -
+

About this lane

Flowcell: @@ -22,6 +22,8 @@
Lane: {{lane.lane_number}}
+ Cycles: + {{lane.flowcell.read_length}}
pM {{ lane.pM }}
Cluster Estimate @@ -31,56 +33,56 @@ {{ lane.status }}
Comment: {{ lane.comment }}
- - -
-

About the library

- Library ID: - {{ lib.id }}
- Name: - {{ lib.library_name }} -
- Species: - {{ lib.library_species.scientific_name }} -
- Concentration: - {{ lib.undiluted_concentration }} ng/µl -
- Gel Cut Size: - {{ lib.gel_cut_size }} -
- Insert Size: - {{ lib.insert_size }} -
- Background or Cell Line: - {{ lib.cell_line }} -
- Replicate: - {{ lib.replicate }} -
- Library Type: - {{ lib.library_type }} -
- Experiment Type: - {{ lib.experiment_type }} -
- Made By: - {{ lib.made_by }} -
- Creation Date - {{ lib.creation_date }} -
- Protocol Stopping Point - {{ lib.stopping_point_name }} -
- Affiliations: -
    - {% for individual in lib.affiliations.all %} -
  • - {{ individual.name }} ( {{ individual.contact }} ) -
  • - {% endfor %} -
+
+ {% include "sample_header.html" %} +
+
+ {% for run in flowcell.datarun_set.all %} +

Run {{ run.runfolder_name }}

+ + + + + + + + + + + + {% for lane_id, lane_file_set in run.lane_files.items %} + {% if lane_id == lane.lane_number %} + + + + + + + + + {% endif %} + {% endfor %} + +
LaneIVC AllIVC CallIVC Percent BaseIVC Percent Base AllIVC Percent Base Called
{{lane_id}} + + + + + + + + + + + + + + + + + + +
+ {% endfor %}
-
{% endblock %} diff --git a/htsworkflow/frontend/templates/sample_header.html b/htsworkflow/frontend/templates/sample_header.html new file mode 100644 index 0000000..8839d71 --- /dev/null +++ b/htsworkflow/frontend/templates/sample_header.html @@ -0,0 +1,83 @@ +
+
+

Library Name

+ Library ID: {{ lib.id }}
+ Name: + {{ lib.library_name }} +
+ Affiliations: +
    + {% for individual in lib.affiliations.all %} +
  • + {{ individual.name }} ( {{ individual.contact }} ) +
  • + {% endfor %} +
+
+
+

Sample Details

+ Species: + {{ lib.library_species.scientific_name }} +
+ Experiment Type: + {{ lib.experiment_type }} +
+ {% if lib.antibody %} + Antibody: + {{ lib.antibody.antibodies }} + {% if lib.antibody.antibodies.nuckname %} + ({{ lib.antibody.nickname }}) + {% endif %} +
+ {% endif %} + {% if lib.cell_line %} + Background or Cell Line: + {{ lib.cell_line }} +
+ {% endif %} + {% if lib.condition %} + Condition: + {{ lib.condition.condition_name }} + {% if lib.condition.nickname %} + ({{ lib.condition.nickname }}) + {% endif %} +
+ {% endif %} + {% if lib.replicate %} + Replicate: + {{ lib.replicate }} +
+ {% endif %} +
+
+

Library Details

+ Library Type: + {{ lib.library_type }} +
+ Creation Date + {{ lib.creation_date }} +
+ Made By: + {{ lib.made_by }} +
+ {% if lib.gel_cut_size %} + Gel Cut Size: + {{ lib.gel_cut_size }} +
+ {% endif %} + {% if lib.insert_size %} + Insert Size: + {{ lib.insert_size }} +
+ {% endif %} + {% if lib.undiluted_concentration %} + Concentration: + {{ lib.undiluted_concentration }} ng/µl +
+ {% endif %} + {% if lib.stopping_point_name %} + Protocol Stopping Point + {{ lib.stopping_point_name }} +
+ {% endif %} +
diff --git a/htsworkflow/frontend/templates/samples/library_detail.html b/htsworkflow/frontend/templates/samples/library_detail.html index f5c4176..dbab436 100644 --- a/htsworkflow/frontend/templates/samples/library_detail.html +++ b/htsworkflow/frontend/templates/samples/library_detail.html @@ -7,152 +7,11 @@ {% block additional_javascript %} {% endblock %} - - {% endblock %} {% block content %} -
-
-

Library Name

- Library ID: {{ lib.id }}
- Name: - {{ lib.library_name }} -
- Affiliations: -
    - {% for individual in lib.affiliations.all %} -
  • - {{ individual.name }} ( {{ individual.contact }} ) -
  • - {% endfor %} -
-
-
-

Sample Details

- Species: - {{ lib.library_species.scientific_name }} -
- Experiment Type: - {{ lib.experiment_type }} -
- {% if lib.antibody %} - Antibody: - {{ lib.antibody.antibodies }} - {% if lib.antibody.antibodies.nuckname %} - ({{ lib.antibody.nickname }}) - {% endif %} -
- {% endif %} - {% if lib.cell_line %} - Background or Cell Line: - {{ lib.cell_line }} -
- {% endif %} - {% if lib.condition %} - Condition: - {{ lib.condition.condition_name }} - {% if lib.condition.nickname %} - ({{ lib.condition.nickname }}) - {% endif %} -
- {% endif %} - {% if lib.replicate %} - Replicate: - {{ lib.replicate }} -
- {% endif %} -
-
-

Library Details

- Library Type: - {{ lib.library_type }} -
- Creation Date - {{ lib.creation_date }} -
- Made By: - {{ lib.made_by }} -
- {% if lib.gel_cut_size %} - Gel Cut Size: - {{ lib.gel_cut_size }} -
- {% endif %} - {% if lib.insert_size %} - Insert Size: - {{ lib.insert_size }} -
- {% endif %} - {% if lib.undiluted_concentration %} - Concentration: - {{ lib.undiluted_concentration }} ng/µl -
- {% endif %} - {% if lib.stopping_point_name %} - Protocol Stopping Point - {{ lib.stopping_point_name }} -
- {% endif %} -
+ {% include "sample_header.html" %} +

Raw Result Files

@@ -227,27 +86,28 @@ - {% for lane in lane_summary_list %} - - - - - - - - - - - - - - - - - - - - + {# ls short for lane summary #} + {% for ls in lane_summary_list %} + + + + + + + + + + + + + + + + + + + + {% endfor %} diff --git a/htsworkflow/frontend/urls.py b/htsworkflow/frontend/urls.py index 8cdb82c..9495ad9 100644 --- a/htsworkflow/frontend/urls.py +++ b/htsworkflow/frontend/urls.py @@ -28,7 +28,7 @@ urlpatterns = patterns('', # Flowcell: (r'^flowcell/(?P\w+)/(?P\w+)/', 'htsworkflow.frontend.experiments.views.flowcell_lane_detail'), - (r'^flowcell/(?P\w+)/', + (r'^flowcell/(?P\w+)/$', 'htsworkflow.frontend.experiments.views.flowcell_detail'), # AnalysTrack: #(r'^analysis/', include('htsworkflow.frontend.analysis.urls')), diff --git a/htsworkflow/pipelines/test/test_retrive_config.py b/htsworkflow/pipelines/test/test_retrive_config.py index 9847ffd..f655779 100644 --- a/htsworkflow/pipelines/test/test_retrive_config.py +++ b/htsworkflow/pipelines/test/test_retrive_config.py @@ -18,13 +18,13 @@ class RetrieveTestCases(TestCase): pass def test_format_gerald(self): - flowcell_request = self.client.get('/experiments/config/303TUAAXX/json', apidata) + flowcell_request = self.client.get('/experiments/config/FC12150/json', apidata) self.failUnlessEqual(flowcell_request.status_code, 200) print dir(flowcell_request) flowcell_info = json.loads(flowcell_request.content) - options = getCombinedOptions(['-f','303TUAAXX','-g',os.getcwd()]) + options = getCombinedOptions(['-f','FC12150','-g',os.getcwd()]) genome_map = {u'Homo sapiens': '/tmp/hg18' } config = format_gerald_config(options, flowcell_info, genome_map)
{{ lane.cycle_width }}{{ lane.flowcell_id }}{{ lane.lane_id }}{% if lane.end %}{{ lane.end }}{% endif %}{{ lane.clusters.0|intcomma }}{{ lane.successful_pm }}{{ lane.reads|intcomma }}{{ lane.no_match|intcomma }}{{ lane.no_match_percent|stringformat:".2f" }}{{ lane.qc_failed|intcomma }}{{ lane.qc_failed_percent|stringformat:".2f" }}{{ lane.match_codes.U0|intcomma }}{{ lane.match_codes.U1|intcomma }}{{ lane.match_codes.U2|intcomma }}{{ lane.unique_reads|intcomma }}{{ lane.match_codes.R0|intcomma }}{{ lane.match_codes.R1|intcomma }}{{ lane.match_codes.R2|intcomma }}{{ lane.repeat_reads|intcomma }}
{{ ls.cycle_width }}{{ ls.flowcell_id }}{{ ls.lane_id }}{% if ls.end %}{{ ls.end }}{% endif %}{{ ls.clusters.0|intcomma }}{{ ls.successful_pm }}{{ ls.reads|intcomma }}{{ ls.no_match|intcomma }}{{ ls.no_match_percent|stringformat:".2f" }}{{ ls.qc_failed|intcomma }}{{ ls.qc_failed_percent|stringformat:".2f" }}{{ ls.match_codes.U0|intcomma }}{{ ls.match_codes.U1|intcomma }}{{ ls.match_codes.U2|intcomma }}{{ ls.unique_reads|intcomma }}{{ ls.match_codes.R0|intcomma }}{{ ls.match_codes.R1|intcomma }}{{ ls.match_codes.R2|intcomma }}{{ ls.repeat_reads|intcomma }}