Convert Rami's DataRun feature to something useful to us.
authorDiane Trout <diane@caltech.edu>
Sat, 11 Jun 2011 00:14:16 +0000 (17:14 -0700)
committerDiane Trout <diane@caltech.edu>
Sat, 11 Jun 2011 00:24:19 +0000 (17:24 -0700)
(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.

16 files changed:
htsworkflow/frontend/experiments/admin.py
htsworkflow/frontend/experiments/fixtures/initial_data.json [new file with mode: 0644]
htsworkflow/frontend/experiments/fixtures/test_flowcells.json
htsworkflow/frontend/experiments/models.py
htsworkflow/frontend/experiments/tests.py
htsworkflow/frontend/experiments/urls.py
htsworkflow/frontend/experiments/views.py
htsworkflow/frontend/samples/models.py
htsworkflow/frontend/samples/views.py
htsworkflow/frontend/static/css/app.css
htsworkflow/frontend/templates/experiments/flowcell_detail.html
htsworkflow/frontend/templates/experiments/flowcell_lane_detail.html
htsworkflow/frontend/templates/sample_header.html [new file with mode: 0644]
htsworkflow/frontend/templates/samples/library_detail.html
htsworkflow/frontend/urls.py
htsworkflow/pipelines/test/test_retrive_config.py

index eb243a70b264ebc371b81235652a39d383eeab00..4181e5c91aef2aa0d2a7af9b2d0bddcc37ad65e6 100644 (file)
@@ -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 (file)
index 0000000..67a6393
--- /dev/null
@@ -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<lane>[0-9])_all\\.png\\Z(?ms)"
+    }
+  },
+  { "model": "experiments.FileType",
+    "pk": 4,
+    "fields": {
+      "name": "IVC Call",
+      "mimetype": "image/png",
+      "regex": "s_(?P<lane>[0-9])_call\\.png\\Z(?ms)"
+    }
+  },
+  { "model": "experiments.FileType",
+    "pk": 5,
+    "fields": {
+      "name": "IVC Percent All",
+      "mimetype": "image/png",
+      "regex": "s_(?P<lane>[0-9])_percent_all\\.png\\Z(?ms)"
+    }
+  },
+  { "model": "experiments.FileType",
+    "pk": 6,
+    "fields": {
+      "name": "IVC Percent Base",
+      "mimetype": "image/png",
+      "regex": "s_(?P<lane>[0-9])_percent_base\\.png\\Z(?ms)"
+    }
+  },
+  { "model": "experiments.FileType",
+    "pk": 7,
+    "fields": {
+      "name": "IVC Percent Call",
+      "mimetype": "image/png",
+      "regex": "s_(?P<lane>[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<lane>[0-9])((?P<end>[1-4])_)?_eland_result\\.txt\\.bz2\\Z(?ms)"
+    }
+  },
+  { "model": "experiments.FileType",
+    "pk": 10,
+    "fields": {
+      "name": "ELAND Multi",
+      "regex": "s_(?P<lane>[0-9])((?P<end>[1-4])_)?_eland_multi\\.txt\\.bz2\\Z(?ms)"
+    }
+  },
+  { "model": "experiments.FileType",
+    "pk": 11,
+    "fields": {
+      "name": "ELAND Extended",
+      "regex": "s_(?P<lane>[0-9])((?P<end>[1-4])_)?_eland_extended\\.txt\\.bz2\\Z(?ms)"
+    }
+  },
+  { "model": "experiments.FileType",
+    "pk": 12,
+    "fields": {
+      "name": "ELAND Export",
+      "regex": "s_(?P<lane>[0-9])((?P<end>[1-4])_)?_export\\.txt\\.bz2\\Z(?ms)"
+    }
+  },
+  { "model": "experiments.FileType",
+    "pk": 13,
+    "fields": {
+      "name": "SRF",
+      "regex": ".*_(?P<lane>[1-8])\\.srf\\Z(?ms)"
+    }
+  },
+  { "model": "experiments.FileType",
+    "pk": 14,
+    "fields": {
+      "name": "QSEQ tarfile",
+      "regex": ".*_l(?P<lane>[1-8])_r(?P<end>[1-4])\\.tar\\.bz2\\Z(?ms)"
+    }
+  }
+
+]
\ No newline at end of file
index 37adf5ed03a2645035286e6092f4d2200f54a1f6..a37fced3f197134abeffebb5eb3c7b56b0dd837a 100644 (file)
  {"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",
index 8a71e252331c600ce12eeaea8b20511f337ad98c..4e060f9330229ed5125313ffadc5f2a7e2ff4121 100755 (executable)
@@ -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 +='<a target=_balnk href="/experiments/'+self.flowcell_id+'" title="Create XLS like sheet for this Flowcell ..." ">Create LOG</a>'
-    try:
-      t = DataRun.objects.get(fcid=self.id)
-      str +='<br/><a target=_self href="/admin/experiments/datarun/?q='+self.flowcell_id+'" title="Check Data Runs ..." ">DataRun ..</a>'
-    except ObjectDoesNotExist:
-      str += '<br/><span style="color:red">not sequenced</span>'
-    return str
-  Create_LOG.allow_tags = True 
-
   def Lanes(self):
-    library_url = '/admin/samples/library/%s' 
     html = ['<table>']
-    #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 = '<tr><td>%d</td><td><a href="%s">%s</a></td><td>%s</td></tr>'
-        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('</table>')
     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 = '<div'
-    if self.run_status >= 5:
-      str += ' style="color:green">'
-      str += '<b>'+self.RUN_STATUS_CHOICES[self.run_status][1]+'</b>'
-      str += '<br/><br/>' #<span style="color:red;font-size:80%;">New!</span>'
-      str +='<br/><a target=_balnk href="'+settings.TASKS_PROJS_SERVER+'/Flowcells/'+self.fcid.flowcell_id+'/'+self.fcid.flowcell_id+'_QC_Summary.html" title="View QC Summaries of this run ..." ">View QC Page</a>'
-    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 += '</div>'
-    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 = '<b>'+self.fcid.__str__()+'</b>'
-    str += '  (c: '+self.fcid.cluster_mac_id+',  s: '+self.fcid.seq_mac_id+')'
-    str += '<div style="margin-top:5px;">'    
-    str +='<a title="View Lane List here ..."  onClick="el = document.getElementById(\'LanesOf'+self.fcid.__str__()+'\');if(el) (el.style.display==\'none\'?el.style.display=\'block\':el.style.display=\'none\')" style="cursor:pointer;color: #5b80b2;">View/hide lanes</a>'
-    str += '<div id="LanesOf'+self.fcid.__str__()+'" style="display:block;border:solid #cccccc 1px;width:350px">'
-    LanesList = '1: '+self.fcid.lane_1_library.__str__()+' ('+self.fcid.lane_1_library.library_species.use_genome_build+')<br/>2: '+self.fcid.lane_2_library.__str__()+' ('+self.fcid.lane_2_library.library_species.use_genome_build+')<br/>3: '+self.fcid.lane_3_library.__str__()+' ('+self.fcid.lane_3_library.library_species.use_genome_build+')<br/>4: '+self.fcid.lane_4_library.__str__()+' ('+self.fcid.lane_4_library.library_species.use_genome_build+')<br/>5: '+self.fcid.lane_5_library.__str__()+' ('+self.fcid.lane_5_library.library_species.use_genome_build+')<br/>6: '+self.fcid.lane_6_library.__str__()+' ('+self.fcid.lane_6_library.library_species.use_genome_build+')<br/>7: '+self.fcid.lane_7_library.__str__()+' ('+self.fcid.lane_7_library.library_species.use_genome_build+')<br/>8: '+self.fcid.lane_8_library.__str__()+' ('+self.fcid.lane_8_library.library_species.use_genome_build+')'
-    str += LanesList ## self.fcid.Lanes()
-    str += '</div>'
-    str += '<div><a title="open Flowcell record" href="/admin/exp_track/flowcell/'+self.fcid.id.__str__()+'/" target=_self>Edit Flowcell record</a>'
-    #str += '<span style="color:red;font-size:80%;margin-left:15px;margin-right:3px">New!</span>'
-    str +='<a style="margin-left:15px;" target=_balnk href="/exp_track/'+self.fcid.flowcell_id+'" title="View XLS like sheet for this Flowcell LOG ..." ">GA LOG Page</a>'
-    str += '</div>'
-    str += '</div>'    
-    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<lane>) and
+    (?P<end>) 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"<FileType: %s>" % (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)
+
index 7bfebfec3f9a5190ef40dac3dacbcb51dcd3bdd9..16e31eef114d1cb562859c84bc00c0362ddde30d 100644 (file)
@@ -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"<FileType: QSEQ tarfile>",
+                             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('<a href="/admin/experiments/flowcell/153/">[^<]+</a>', response.content))
         
index 5119e9d3c14006b741586f400bae4fef5f6a6b35..91202a867bd76bad92dad516d81869ba70c953b3 100755 (executable)
@@ -6,14 +6,8 @@ urlpatterns = patterns('',
     #(r'^(?P<run_folder>.+)/$', 'gaworkflow.frontend.experiments.views.detail'),
     (r'^config/(?P<fc_id>.+)/json$', 'htsworkflow.frontend.experiments.experiments.flowcell_json'),
     (r'^lanes_for/(?P<username>.+)/json$', 'htsworkflow.frontend.experiments.experiments.lanes_for_json'),
-    (r'^fcsheet/(?P<fcid>.+)/$', '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<key>.+)/?$', 'htsworkflow.frontend.experiments.views.read_result_file'),
     (r'^started/(?P<pk>.+)/$', 'htsworkflow.frontend.experiments.views.startedEmail'),
     (r'^finished/(?P<pk>.+)/$', 'htsworkflow.frontend.experiments.views.finishedEmail'),
+                        
 )
index 342438db356b4140a04c59b3195c2ee3252bb158..827f5219ef05870f65735a82077e9b373def1028 100755 (executable)
@@ -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
+      
+  
index e49d23b16e98cde532377ae7ab08d24bfb7bb7f1..838bea62cfcb9004449d759f1abed644a6f13c8c 100644 (file)
@@ -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):
     """
index 2bbcc894a9c28ea5733f3ff62cdbebe1222bb480..c48826c0e2b884109f73d8c3aeddd15ea3c51351 100644 (file)
@@ -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):
     """
index fec1733829d7739f9f71fad34f8a858f58657a36..0a86f8ab2645f7554e74a2354e8989b9818745eb 100644 (file)
@@ -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;
   }
- /* ]]> */
-</style>
+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; }
+
index 3c9329cbb873f8444d06d6ccc5a1ad8c6ab92858..b238f8b95f1177575476aae28a76bdb964366807 100644 (file)
@@ -24,7 +24,7 @@
     <span property="libns:control_lane">{{flowcell.control_lane}}</span><br/>
 
   <b>Notes</b>:
-    <p property="libns:flowcell_notes">{{flowcell.notes}}</p>
+    <pre property="libns:flowcell_notes">{{flowcell.notes}}</pre>
   <div class="htswdetail">
     <h2>Lanes</h2>
     <table>
       </tbody>
     </table>
     </div>
+    <div class="htsw_flowcell_ivc">
+    {% for run in flowcell.datarun_set.all %}
+       <h2>Run {{ run.runfolder_name }}</h2>
+       <table>
+         <thead>
+           <tr>
+             <td>Lane</td>
+             <td>IVC All</td>
+             <td>IVC Call</td>
+             <td>IVC Percent Base</td>
+             <td>IVC Percent Base All</td>
+             <td>IVC Percent Base Called</td>
+         </thead>
+         <tbody>
+            {% for lane_id, lane_file_set in run.lane_files.items %}
+            <tr>
+              <td>{{ lane_id }}</td>
+              <td>
+                <a href="{{ lane_file_set.ivc_all.get_absolute_url }}">
+                <img height="84" width="126" src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
+              </td>
+              <td>
+                <a href="{{ lane_file_set.ivc_call.get_absolute_url }}">
+                <img height="84" width="126" src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
+                </a>
+              </td>
+              <td>
+                <a href="{{ lane_file_set.ivc_percent_base.get_absolute_url }}">
+                <img height="84" width="126" src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
+                </a>
+              </td>
+              <td>
+                <a href="{{ lane_file_set.ivc_percent_all.get_absolute_url }}">
+                <img height="84" width="126" src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
+                </a>
+              </td>
+              <td>
+                <a href="{{ lane_file_set.ivc_percent_call.get_absolute_url }}">
+                <img height="84" width="126" src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
+                </a>
+              </td>
+            </tr>
+            {% endfor %} 
+         </tbody>
+       </table>
+    {% endfor %}
+    </div>
 </div>  
 {% endblock %}
index aceb186dcadcc9ca4e946345fc2d0bd186455f08..f65c9e930cf95b96ddd0e34166e821664e4234b6 100644 (file)
@@ -10,7 +10,7 @@
 {% endblock %}
 
 {% block content %}
-<div id="lane_detail">
+<div id="lane_detail" class="htswdetail">
   <h2>About this lane</h2>
   <div rel="libns:flowcell" resource="{{flowcell.get_absolute_url}}">
   <b>Flowcell</b>: 
@@ -22,6 +22,8 @@
   </div>
   <b>Lane</b>: 
     <span property="libns:lane_number" datatype="xsd:decimal">{{lane.lane_number}}</span><br/>
+  <b>Cycles</b>: 
+    <span property="libns:read_length">{{lane.flowcell.read_length}}</span><br/>
   <b>pM</b>
     <span property="libns:pM" datatype="xsd:decimal">{{ lane.pM }}</span><br/>
   <b>Cluster Estimate</b>
     <span property="libns:status">{{ lane.status }}</span><br/>
   <b>Comment</b>: 
     <span property="libns:comment">{{ lane.comment }}</span><br/>
-
-
-  <div rel="libns:library" resource="{{lib.get_absolute_url}}">
-  <h2>About the library</h2>
-  <b>Library ID</b>: 
-    <a href="{{lib.get_absolute_url}}" property="libns:library_id">{{ lib.id }}</a><br/>
-  <b>Name</b>: 
-    <span property="libns:name">{{ lib.library_name }}</span>
-  <br/>
-  <b>Species</b>: 
-    <a href="{{lib.library_species.get_absolute_url}}" rel="libns:species"><span property="libns:species_name">{{ lib.library_species.scientific_name }}</span></a>
-  <br/>
-  <b>Concentration</b>: 
-    <span property="libns:concentration">{{ lib.undiluted_concentration }} ng/µl</span>
-  <br/>
-  <b>Gel Cut Size</b>: 
-    <span property="libns:gel_cut">{{ lib.gel_cut_size }}</span>
-  <br/>
-  <b>Insert Size</b>: 
-    <span property="libns:insert_size">{{ lib.insert_size }}</span>
-  <br/>
-  <b>Background or Cell Line</b>:
-     <span property="libns:cell_line">{{ lib.cell_line }}</span>
-  <br/>
-  <b>Replicate</b>: 
-     <span property="libns:replicate">{{ lib.replicate }}</span>
-  <br/>
-  <b>Library Type</b>:
-     <span property="libns:library_type">{{ lib.library_type }}</span>
-  <br/>
-  <b>Experiment Type</b>:
-     <span property="libns:experiment_type">{{ lib.experiment_type }}</span>
-  <br/>
-  <b>Made By</b>: 
-    <span property="libns:made_by">{{ lib.made_by }}</span>
-  <br/>
-  <b>Creation Date</b>
-    <span property="libns:date" content="{{lib.creation_date|date:'Y-m-d'}}T00:00:00" datatype="xsd:dateTime">{{ lib.creation_date }}</span>
-  <br/> 
-  <b>Protocol Stopping Point</b>
-    <span property="libns:stopping_point">{{ lib.stopping_point_name }}</span>
-  <br/> 
-  <b>Affiliations</b>:
-  <ul>
-    {% for individual in lib.affiliations.all %}
-      <li property="libns:affliation" content="{{individual.name}}">
-        {{ individual.name }} ( {{ individual.contact }} )
-      </li>
-    {% endfor %}
-  </ul>
+  <hr/>
+  {% include "sample_header.html" %}
+  <hr/>
+  <div class="htsw_flowcell_ivc">
+  {% for run in flowcell.datarun_set.all %}
+     <h2>Run {{ run.runfolder_name }}</h2>
+     <table>
+       <thead>
+         <tr>
+           <td>Lane</td>
+           <td>IVC All</td>
+           <td>IVC Call</td>
+           <td>IVC Percent Base</td>
+           <td>IVC Percent Base All</td>
+           <td>IVC Percent Base Called</td>
+       </thead>
+       <tbody>
+          {% for lane_id, lane_file_set in run.lane_files.items %}
+          {% if lane_id == lane.lane_number %}
+          <tr>
+            <td>{{lane_id}}</td>
+            <td>
+              <a href="{{ lane_file_set.ivc_all.get_absolute_url }}">
+              <img height="84" width="126" src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
+            </td>
+            <td>
+              <a href="{{ lane_file_set.ivc_call.get_absolute_url }}">
+              <img height="84" width="126" src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
+              </a>
+            </td>
+            <td>
+              <a href="{{ lane_file_set.ivc_percent_base.get_absolute_url }}">
+              <img height="84" width="126" src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
+              </a>
+            </td>
+            <td>
+              <a href="{{ lane_file_set.ivc_percent_all.get_absolute_url }}">
+              <img height="84" width="126" src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
+              </a>
+            </td>
+            <td>
+              <a href="{{ lane_file_set.ivc_percent_call.get_absolute_url }}">
+              <img height="84" width="126" src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
+              </a>
+            </td>
+          </tr>
+          {% endif %}
+          {% endfor %} 
+       </tbody>
+     </table>
+  {% endfor %}
   </div>
-</div>  
 {% endblock %}
diff --git a/htsworkflow/frontend/templates/sample_header.html b/htsworkflow/frontend/templates/sample_header.html
new file mode 100644 (file)
index 0000000..8839d71
--- /dev/null
@@ -0,0 +1,83 @@
+<div id="librarydetail">
+  <div class="library_identity">
+    <h2>Library Name</h2>  
+    <b>Library ID</b>: <a href="{{lib.get_absolute_url}}">{{ lib.id }}</a><br/>
+    <b>Name</b>: 
+      <span property="libns:name">{{ lib.library_name }}</span>
+    <br/>
+    <b>Affiliations</b>:
+    <ul>
+      {% for individual in lib.affiliations.all %}
+        <li property="libns:affiliation" content="{{individual.name}}">
+          {{ individual.name }} ( {{ individual.contact }} )
+        </li>
+      {% endfor %}
+    </ul>
+  </div>
+  <div class="library_sample_detail">
+    <h2>Sample Details</h2>
+    <b>Species</b>: 
+      <a href="{{lib.library_species.get_absolute_url}}" property="libns:species">{{ lib.library_species.scientific_name }}</a>
+    <br/>
+    <b>Experiment Type</b>:
+       <span property="libns:experiment_type">{{ lib.experiment_type }}</span>
+    <br/>
+    {% if lib.antibody %}
+    <b>Antibody</b>:
+       <span property="libns:antibody">{{ lib.antibody.antibodies }}</span>
+       {% if lib.antibody.antibodies.nuckname %}
+       (<span property="libns:antibody_term">{{ lib.antibody.nickname }}</span>)
+       {% endif %}
+    <br/>
+    {% endif %}
+    {% if lib.cell_line %}
+    <b>Background or Cell Line</b>:
+       <span property="libns:cell_line">{{ lib.cell_line }}</span>
+    <br/>
+    {% endif %}
+    {% if lib.condition %}
+    <b>Condition</b>:
+       <span property="libns:condition">{{ lib.condition.condition_name }}</span>
+       {% if lib.condition.nickname %}
+       (<span property="libns:condition_term">{{ lib.condition.nickname }}</span>)
+       {% endif %}
+    <br/>
+    {% endif %}
+    {% if lib.replicate %}
+    <b>Replicate</b>: 
+       <span property="libns:replicate">{{ lib.replicate }}</span>
+    <br/>
+    {% endif %}
+  </div>
+  <div class="library_library_detail">
+    <h2>Library Details</h2>
+    <b>Library Type</b>:
+       <span property="libns:library_type">{{ lib.library_type }}</span>
+    <br/>
+    <b>Creation Date</b>
+      <span property="libns:date" content="{{lib.creation_date|date:'Y-m-d'}}T00:00:00" datatype="xsd:dateTime">{{ lib.creation_date }}</span>
+    <br/> 
+    <b>Made By</b>: 
+      <span property="libns:made_by">{{ lib.made_by }}</span>
+    <br/>
+    {% if lib.gel_cut_size %}
+    <b>Gel Cut Size</b>: 
+      <span property="libns:gel_cut">{{ lib.gel_cut_size }}</span>
+    <br/>
+    {% endif %}
+    {% if lib.insert_size %}
+    <b>Insert Size</b>: 
+      <span property="libns:insert_size">{{ lib.insert_size }}</span>
+    <br/>
+    {% endif %}
+    {% if lib.undiluted_concentration %}
+    <b>Concentration</b>: 
+      <span property="libns:concentration">{{ lib.undiluted_concentration }} ng/µl</span>
+    <br/>
+    {% endif %}
+    {% if lib.stopping_point_name %}
+    <b>Protocol Stopping Point</b>
+      <span property="libns:stopping_point">{{ lib.stopping_point_name }}</span>
+    <br/>
+    {% endif %}
+  </div>
index f5c4176855cd071950e2819add3e4f5a3d6693e6..dbab436671e60d0cfd4b007f85634d2830dedc07 100644 (file)
     
     {% block additional_javascript %}
     {% endblock %}
-
-<style type="text/css">
-  /* <![CDATA[ */
-  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; }
- /* ]]> */
-</style>
 {% endblock %}
 
 {% block content %}
-<div id="librarydetail">
-  <div class="library_identity">
-    <h2>Library Name</h2>  
-    <b>Library ID</b>: {{ lib.id }}<br/>
-    <b>Name</b>: 
-      <span property="libns:name">{{ lib.library_name }}</span>
-    <br/>
-    <b>Affiliations</b>:
-    <ul>
-      {% for individual in lib.affiliations.all %}
-        <li property="libns:affiliation" content="{{individual.name}}">
-          {{ individual.name }} ( {{ individual.contact }} )
-        </li>
-      {% endfor %}
-    </ul>
-  </div>
-  <div class="library_sample_detail">
-    <h2>Sample Details</h2>
-    <b>Species</b>: 
-      <span property="libns:species">{{ lib.library_species.scientific_name }}</span>
-    <br/>
-    <b>Experiment Type</b>:
-       <span property="libns:experiment_type">{{ lib.experiment_type }}</span>
-    <br/>
-    {% if lib.antibody %}
-    <b>Antibody</b>:
-       <span property="libns:antibody">{{ lib.antibody.antibodies }}</span>
-       {% if lib.antibody.antibodies.nuckname %}
-       (<span property="libns:antibody_term">{{ lib.antibody.nickname }}</span>)
-       {% endif %}
-    <br/>
-    {% endif %}
-    {% if lib.cell_line %}
-    <b>Background or Cell Line</b>:
-       <span property="libns:cell_line">{{ lib.cell_line }}</span>
-    <br/>
-    {% endif %}
-    {% if lib.condition %}
-    <b>Condition</b>:
-       <span property="libns:condition">{{ lib.condition.condition_name }}</span>
-       {% if lib.condition.nickname %}
-       (<span property="libns:condition_term">{{ lib.condition.nickname }}</span>)
-       {% endif %}
-    <br/>
-    {% endif %}
-    {% if lib.replicate %}
-    <b>Replicate</b>: 
-       <span property="libns:replicate">{{ lib.replicate }}</span>
-    <br/>
-    {% endif %}
-  </div>
-  <div class="library_library_detail">
-    <h2>Library Details</h2>
-    <b>Library Type</b>:
-       <span property="libns:library_type">{{ lib.library_type }}</span>
-    <br/>
-    <b>Creation Date</b>
-      <span property="libns:date" content="{{lib.creation_date|date:'Y-m-d'}}T00:00:00" datatype="xsd:dateTime">{{ lib.creation_date }}</span>
-    <br/> 
-    <b>Made By</b>: 
-      <span property="libns:made_by">{{ lib.made_by }}</span>
-    <br/>
-    {% if lib.gel_cut_size %}
-    <b>Gel Cut Size</b>: 
-      <span property="libns:gel_cut">{{ lib.gel_cut_size }}</span>
-    <br/>
-    {% endif %}
-    {% if lib.insert_size %}
-    <b>Insert Size</b>: 
-      <span property="libns:insert_size">{{ lib.insert_size }}</span>
-    <br/>
-    {% endif %}
-    {% if lib.undiluted_concentration %}
-    <b>Concentration</b>: 
-      <span property="libns:concentration">{{ lib.undiluted_concentration }} ng/µl</span>
-    <br/>
-    {% endif %}
-    {% if lib.stopping_point_name %}
-    <b>Protocol Stopping Point</b>
-      <span property="libns:stopping_point">{{ lib.stopping_point_name }}</span>
-    <br/>
-    {% endif %}
-  </div>
+  {% include "sample_header.html" %}
+  <hr/>
   <div class="library_statistics">
   <h2>Raw Result Files</h2>
   <table>
     </thead>
     <tbody>
   
-      {% for lane in lane_summary_list %}
-      <tr about="/flowcell/{{lane.flowcell_id}}/{{lane.lane_id}}/{% if lane.end %}#end{{ lane.end }}{% endif %}">
-        <td>{{ lane.cycle_width }}</td>
-        <td>{{ lane.flowcell_id }}</td>
-        <td>{{ lane.lane_id }}</td>
-        <td>{% if lane.end %}{{ lane.end }}{% endif %}</td>
-        <td>{{ lane.clusters.0|intcomma }}</td>
-        <td>{{ lane.successful_pm }}</td>
-        <td>{{ lane.reads|intcomma }}</td>
-        <td>{{ lane.no_match|intcomma }}</td>
-        <td>{{ lane.no_match_percent|stringformat:".2f" }}</td>
-        <td>{{ lane.qc_failed|intcomma }}</td>
-        <td>{{ lane.qc_failed_percent|stringformat:".2f" }}</td>
-        <td>{{ lane.match_codes.U0|intcomma }}</td>
-        <td>{{ lane.match_codes.U1|intcomma }}</td>
-        <td>{{ lane.match_codes.U2|intcomma }}</td>
-        <td {% if lane.unique_reads %}property="libns:total_unique_locations" content="{{lane.unique_reads}}" datatype="xsd:decimal"{% endif %}>{{ lane.unique_reads|intcomma }}</td>
-        <td>{{ lane.match_codes.R0|intcomma }}</td>
-        <td>{{ lane.match_codes.R1|intcomma }}</td>
-        <td>{{ lane.match_codes.R2|intcomma }}</td>
-        <td>{{ lane.repeat_reads|intcomma }}</td>
+      {# ls short for lane summary #}
+      {% for ls in lane_summary_list %}
+      <tr about="{{ls.lane.get_absolute_url">
+        <td>{{ ls.cycle_width }}</td>
+        <td><a href="{{ls.flowcell.get_absolute_url}}">{{ ls.flowcell_id }}</a></td>
+        <td><a href="{{ls.lane.get_absolute_url}}">{{ ls.lane_id }}</a></td>
+        <td>{% if ls.end %}{{ ls.end }}{% endif %}</td>
+        <td>{{ ls.clusters.0|intcomma }}</td>
+        <td>{{ ls.successful_pm }}</td>
+        <td>{{ ls.reads|intcomma }}</td>
+        <td>{{ ls.no_match|intcomma }}</td>
+        <td>{{ ls.no_match_percent|stringformat:".2f" }}</td>
+        <td>{{ ls.qc_failed|intcomma }}</td>
+        <td>{{ ls.qc_failed_percent|stringformat:".2f" }}</td>
+        <td>{{ ls.match_codes.U0|intcomma }}</td>
+        <td>{{ ls.match_codes.U1|intcomma }}</td>
+        <td>{{ ls.match_codes.U2|intcomma }}</td>
+        <td {% if ls.unique_reads %}property="libns:total_unique_locations" content="{{ls.unique_reads}}" datatype="xsd:decimal"{% endif %}>{{ ls.unique_reads|intcomma }}</td>
+        <td>{{ ls.match_codes.R0|intcomma }}</td>
+        <td>{{ ls.match_codes.R1|intcomma }}</td>
+        <td>{{ ls.match_codes.R2|intcomma }}</td>
+        <td>{{ ls.repeat_reads|intcomma }}</td>
       </tr>
       {% endfor %}
     </tbody>
index 8cdb82c83936fb1c0859765d9bf09b3d31c1ef65..9495ad98c08103b22a05046d8c9a7de58f362d4d 100644 (file)
@@ -28,7 +28,7 @@ urlpatterns = patterns('',
     # Flowcell:
     (r'^flowcell/(?P<flowcell_id>\w+)/(?P<lane_number>\w+)/',
      'htsworkflow.frontend.experiments.views.flowcell_lane_detail'),
-    (r'^flowcell/(?P<flowcell_id>\w+)/',
+    (r'^flowcell/(?P<flowcell_id>\w+)/$',
      'htsworkflow.frontend.experiments.views.flowcell_detail'),
     # AnalysTrack:
     #(r'^analysis/', include('htsworkflow.frontend.analysis.urls')),
index 9847ffd4aea53d7a613bfe266cc882584c1c3a7d..f655779bbb6edaf2c00a56e66284331f19215b4b 100644 (file)
@@ -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)