Add support for tracking the multiplex index sequence. 0.5.4
authorDiane Trout <diane@caltech.edu>
Sat, 17 Sep 2011 00:26:58 +0000 (17:26 -0700)
committerDiane Trout <diane@caltech.edu>
Sat, 17 Sep 2011 00:26:58 +0000 (17:26 -0700)
I had to add a new table to store the sequence Samples.MultiplexIndex
its manually searched by library type and ID.

There's also some extra complexity in that the multiplex id field
allows entering a comma seperated list to handle the cases where there are
samples that are pooled before being given to us. (As our library actually
represents a single physical tube).

This also adds a --sample-sheet option to attempt to create a sample sheet
for demultiplexing samples.

docs/v0.5.3_to_v0.5.4.sql [new file with mode: 0644]
htsworkflow/frontend/experiments/experiments.py
htsworkflow/frontend/experiments/fixtures/test_flowcells.json
htsworkflow/frontend/experiments/tests.py
htsworkflow/frontend/samples/admin.py
htsworkflow/frontend/samples/fixtures/initial_data.json
htsworkflow/frontend/samples/models.py
htsworkflow/frontend/templates/sample_header.html
htsworkflow/pipelines/retrieve_config.py
htsworkflow/pipelines/test/test_retrive_config.py
scripts/htsw-get-config

diff --git a/docs/v0.5.3_to_v0.5.4.sql b/docs/v0.5.3_to_v0.5.4.sql
new file mode 100644 (file)
index 0000000..031816b
--- /dev/null
@@ -0,0 +1,46 @@
+-- Add fields to support multiplexing, and update our database
+
+alter table samples_library add column multiplex_id varchar(128);
+alter table samples_librarytype add column can_multiplex bool not null default false;
+alter table samples_librarytype add column is_paired_end bool not null default false;
+update samples_librarytype set can_multiplex=0, is_paired_end=0;
+update samples_librarytype set can_multiplex=1 where id in (5,7,8);
+update samples_librarytype set is_paired_end=1 where id in (2,5,7,8);
+update samples_library set multiplex_id=1 where library_name like "Index #1 %";
+update samples_library set multiplex_id=2 where library_name like "Index #2 %";
+update samples_library set multiplex_id=3 where library_name like "Index #3 %";
+update samples_library set multiplex_id=4 where library_name like "Index #4 %";
+update samples_library set multiplex_id=5 where library_name like "Index #5 %";
+update samples_library set multiplex_id=6 where library_name like "Index #6 %";
+update samples_library set multiplex_id=7 where library_name like "Index #7 %";
+update samples_library set multiplex_id=8 where library_name like "Index #8 %";
+update samples_library set multiplex_id=9 where library_name like "Index #9 %";
+update samples_library set multiplex_id=10 where library_name like "Index #10 %";
+update samples_library set multiplex_id=11 where library_name like "Index #11 %";
+update samples_library set multiplex_id=12 where library_name like "Index #12 %";
+
+update samples_library set multiplex_id=1 where library_name like "Nextera #1 %";
+update samples_library set multiplex_id=2 where library_name like "Nextera #2 %";
+update samples_library set multiplex_id=3 where library_name like "Nextera #3 %";
+update samples_library set multiplex_id=4 where library_name like "Nextera #4 %";
+update samples_library set multiplex_id=5 where library_name like "Nextera #5 %";
+update samples_library set multiplex_id=6 where library_name like "Nextera #6 %";
+update samples_library set multiplex_id=7 where library_name like "Nextera #7 %";
+update samples_library set multiplex_id=8 where library_name like "Nextera #8 %";
+update samples_library set multiplex_id=9 where library_name like "Nextera #9 %";
+update samples_library set multiplex_id=10 where library_name like "Nextera #10 %";
+update samples_library set multiplex_id=11 where library_name like "Nextera #11 %";
+update samples_library set multiplex_id=12 where library_name like "Nextera #12 %";
+
+update samples_library set multiplex_id=1 where library_name like "Nextera index1 %";
+update samples_library set multiplex_id=2 where library_name like "Nextera index2 %";
+update samples_library set multiplex_id=3 where library_name like "Nextera index3 %";
+update samples_library set multiplex_id=4 where library_name like "Nextera index4 %";
+update samples_library set multiplex_id=5 where library_name like "Nextera index5 %";
+update samples_library set multiplex_id=6 where library_name like "Nextera index6 %";
+update samples_library set multiplex_id=7 where library_name like "Nextera index7 %";
+update samples_library set multiplex_id=8 where library_name like "Nextera index8 %";
+update samples_library set multiplex_id=9 where library_name like "Nextera index9 %";
+update samples_library set multiplex_id=10 where library_name like "Nextera index10 %";
+update samples_library set multiplex_id=11 where library_name like "Nextera index11 %";
+update samples_library set multiplex_id=12 where library_name like "Nextera index12 %";
index 5dec8cf5871bc640050c68b1118da2a3c073b400..3cdbc7bd79eb425c7ad85f7f8eb78726e2477313 100755 (executable)
@@ -4,7 +4,7 @@ try:
     import json
 except ImportError, e:
     import simplejson as json
     import json
 except ImportError, e:
     import simplejson as json
-    
+
 import os
 import re
 
 import os
 import re
 
@@ -20,7 +20,7 @@ from htsworkflow.frontend.experiments.models import \
     DataRun, \
     Lane, \
     LANE_STATUS_MAP
     DataRun, \
     Lane, \
     LANE_STATUS_MAP
-from htsworkflow.frontend.samples.models import Library, HTSUser
+from htsworkflow.frontend.samples.models import Library, MultiplexIndex, HTSUser
 
 def flowcell_information(flowcell_id):
     """
 
 def flowcell_information(flowcell_id):
     """
@@ -33,13 +33,13 @@ def flowcell_information(flowcell_id):
 
     lane_set = {}
     for lane in fc.lane_set.all():
 
     lane_set = {}
     for lane in fc.lane_set.all():
-        lane_set[lane.lane_number] = {
+        lane_item = {
             'cluster_estimate': lane.cluster_estimate,
             'comment': lane.comment,
             'experiment_type': lane.library.experiment_type.name,
             'experiment_type_id': lane.library.experiment_type_id,
             'flowcell': lane.flowcell.flowcell_id,
             'cluster_estimate': lane.cluster_estimate,
             'comment': lane.comment,
             'experiment_type': lane.library.experiment_type.name,
             'experiment_type_id': lane.library.experiment_type_id,
             'flowcell': lane.flowcell.flowcell_id,
-            'lane_number': int(lane.lane_number),
+            'lane_number': lane.lane_number,
             'library_name': lane.library.library_name,
             'library_id': lane.library.id,
             'library_species': lane.library.library_species.scientific_name,
             'library_name': lane.library.library_name,
             'library_id': lane.library.id,
             'library_species': lane.library.library_species.scientific_name,
@@ -48,12 +48,17 @@ def flowcell_information(flowcell_id):
             'status_code': lane.status,
             'status': LANE_STATUS_MAP[lane.status]
         }
             'status_code': lane.status,
             'status': LANE_STATUS_MAP[lane.status]
         }
+        sequences = lane.library.index_sequences()
+        if sequences is not None:
+            lane_item['index_sequence'] = sequences
+
+        lane_set.setdefault(lane.lane_number,[]).append(lane_item)
 
     if fc.control_lane is None:
         control_lane = None
     else:
         control_lane = int(fc.control_lane)
 
     if fc.control_lane is None:
         control_lane = None
     else:
         control_lane = int(fc.control_lane)
-        
+
     info = {
         'advanced_run': fc.advanced_run,
         'cluster_station_id': fc.cluster_station_id,
     info = {
         'advanced_run': fc.advanced_run,
         'cluster_station_id': fc.cluster_station_id,
@@ -70,7 +75,7 @@ def flowcell_information(flowcell_id):
         'sequencer_id': fc.sequencer_id,
         'sequencer': fc.sequencer.name,
     }
         'sequencer_id': fc.sequencer_id,
         'sequencer': fc.sequencer.name,
     }
-    
+
     return info
 
 def flowcell_json(request, fc_id):
     return info
 
 def flowcell_json(request, fc_id):
@@ -78,12 +83,12 @@ def flowcell_json(request, fc_id):
     Return a JSON blob containing enough information to generate a config file.
     """
     require_api_key(request)
     Return a JSON blob containing enough information to generate a config file.
     """
     require_api_key(request)
-    
+
     fc_dict = flowcell_information(fc_id)
 
     if fc_dict is None:
         raise Http404
     fc_dict = flowcell_information(fc_id)
 
     if fc_dict is None:
         raise Http404
-    
+
     fc_json = json.dumps(fc_dict)
     return HttpResponse(fc_json, mimetype = 'application/json')
 
     fc_json = json.dumps(fc_dict)
     return HttpResponse(fc_json, mimetype = 'application/json')
 
@@ -93,12 +98,12 @@ def lanes_for(username=None):
     """
     query = {}
     if username is not None:
     """
     query = {}
     if username is not None:
-        user = HTSUser.objects.get(username=username)        
+        user = HTSUser.objects.get(username=username)
         query.update({'library__affiliations__users__id': user.id})
         query.update({'library__affiliations__users__id': user.id})
-        
+
     lanes = Lane.objects.filter(**query).order_by('-flowcell__run_date')
 
     lanes = Lane.objects.filter(**query).order_by('-flowcell__run_date')
 
-    
+
     result = []
     for l in lanes:
         affiliations = l.library.affiliations.all()
     result = []
     for l in lanes:
         affiliations = l.library.affiliations.all()
@@ -122,12 +127,13 @@ def lanes_for_json(request, username):
         result = lanes_for(username)
     except ObjectDoesNotExist, e:
         raise Http404
         result = lanes_for(username)
     except ObjectDoesNotExist, e:
         raise Http404
-    
+
     #convert query set to python structure
     #convert query set to python structure
-    
+
     result_json = json.dumps(result)
     return HttpResponse(result_json, mimetype='application/json')
     result_json = json.dumps(result)
     return HttpResponse(result_json, mimetype='application/json')
-                 
+
+
 def updStatus(request):
     output=''
     user = 'none'
 def updStatus(request):
     output=''
     user = 'none'
@@ -141,7 +147,7 @@ def updStatus(request):
       user = request.user
 
     #Check access permission
       user = request.user
 
     #Check access permission
-    if not (user.is_superuser and settings.ALLOWED_IPS.has_key(ClIP)): 
+    if not (user.is_superuser and settings.ALLOWED_IPS.has_key(ClIP)):
         return HttpResponse("%s access denied from %s." % (user, ClIP))
 
     # ~~~~~~Parameters for the job ~~~~
         return HttpResponse("%s access denied from %s." % (user, ClIP))
 
     # ~~~~~~Parameters for the job ~~~~
@@ -149,22 +155,22 @@ def updStatus(request):
       fcid = request.REQUEST['fcid']
     else:
       return HttpResponse('missing fcid')
       fcid = request.REQUEST['fcid']
     else:
       return HttpResponse('missing fcid')
-    
+
     if request.REQUEST.has_key('runf'):
       runfolder = request.REQUEST['runf']
     else:
       return HttpResponse('missing runf')
 
     if request.REQUEST.has_key('runf'):
       runfolder = request.REQUEST['runf']
     else:
       return HttpResponse('missing runf')
 
-    
+
     if request.REQUEST.has_key('updst'):
       UpdatedStatus = request.REQUEST['updst']
     else:
       return HttpResponse('missing status')
     if request.REQUEST.has_key('updst'):
       UpdatedStatus = request.REQUEST['updst']
     else:
       return HttpResponse('missing status')
-    
-    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     # Update Data Run status in DB
 
     # Update Data Run status in DB
-    # Try get rec. If not found return 'entry not found + <fcid><runfolder>', if found try update and return updated 
+    # Try get rec. If not found return 'entry not found + <fcid><runfolder>', if found try update and return updated
     try:
       rec = DataRun.objects.get(run_folder=runfolder)
       rec.run_status = UpdatedStatus
     try:
       rec = DataRun.objects.get(run_folder=runfolder)
       rec.run_status = UpdatedStatus
@@ -204,15 +210,15 @@ def generateConfile(request,fcid):
     config += ['ELAND_MULTIPLE_INSTANCES 8']
     genome_dir = 'GENOME_DIR /Volumes/Genomes/'
     eland_genome = 'ELAND_GENOME /Volumes/Genomes/'
     config += ['ELAND_MULTIPLE_INSTANCES 8']
     genome_dir = 'GENOME_DIR /Volumes/Genomes/'
     eland_genome = 'ELAND_GENOME /Volumes/Genomes/'
-    
-    try:                                                                                                                                              
+
+    try:
       fc = FlowCell.objects.get(flowcell_id=fcid)
       for lane in fc.lane_set.all():
           config += [ str(lane.lane_number) +":" + \
                       genome_dir + lane.library.library_species.scientific_name ]
           config += [ str(lane.lane_number) +":" + \
                       eland_genome + lane.library.library_species.scientific_name ]
       fc = FlowCell.objects.get(flowcell_id=fcid)
       for lane in fc.lane_set.all():
           config += [ str(lane.lane_number) +":" + \
                       genome_dir + lane.library.library_species.scientific_name ]
           config += [ str(lane.lane_number) +":" + \
                       eland_genome + lane.library.library_species.scientific_name ]
-      
+
     except ObjectDoesNotExist:
       config = 'Entry not found for fcid  = '+fcid
 
     except ObjectDoesNotExist:
       config = 'Entry not found for fcid  = '+fcid
 
@@ -245,8 +251,8 @@ def getConfile(req):
               rec.config_params = cnfgfile
               rec.save()
             else:
               rec.config_params = cnfgfile
               rec.save()
             else:
-              cnfgfile = 'Failed generating config params for RunFolder = '+runfolder +', Flowcell id = '+ fcid+ ' Config Text:\n'+cnfgfile  
-            
+              cnfgfile = 'Failed generating config params for RunFolder = '+runfolder +', Flowcell id = '+ fcid+ ' Config Text:\n'+cnfgfile
+
         except ObjectDoesNotExist:
           cnfgfile = 'Entry not found for RunFolder = '+runfolder
 
         except ObjectDoesNotExist:
           cnfgfile = 'Entry not found for RunFolder = '+runfolder
 
@@ -264,14 +270,14 @@ def getLaneLibs(req):
     outputfile = ''
     if request.has_key('fcid'):
       fcid = request['fcid']
     outputfile = ''
     if request.has_key('fcid'):
       fcid = request['fcid']
-      try:                                
+      try:
         rec = FlowCell.objects.get(flowcell_id=fcid)
         #Ex: 071211
         year = datetime.today().year.__str__()
         year = replace(year,'20','')
         month = datetime.today().month
         if month < 10: month = "0"+month.__str__()
         rec = FlowCell.objects.get(flowcell_id=fcid)
         #Ex: 071211
         year = datetime.today().year.__str__()
         year = replace(year,'20','')
         month = datetime.today().month
         if month < 10: month = "0"+month.__str__()
-        else: month = month.__str__() 
+        else: month = month.__str__()
         day = datetime.today().day
         if day < 10: day = "0"+day.__str__()
         else: day = day.__str__()
         day = datetime.today().day
         if day < 10: day = "0"+day.__str__()
         else: day = day.__str__()
@@ -302,7 +308,7 @@ def estimateFlowcellDuration(flowcell):
     sequencing_seconds_per_cycle= 3600 * 1.5
     # 800 is a rough guess
     pipeline_seconds_per_cycle = 800
     sequencing_seconds_per_cycle= 3600 * 1.5
     # 800 is a rough guess
     pipeline_seconds_per_cycle = 800
-    
+
     cycles = flowcell.read_length
     if flowcell.paired_end:
         cycles *= 2
     cycles = flowcell.read_length
     if flowcell.paired_end:
         cycles *= 2
@@ -314,7 +320,7 @@ def estimateFlowcellDuration(flowcell):
 
 def estimateFlowcellTimeRemaining(flowcell):
     estimate_mid = estimateFlowcellDuration(flowcell)
 
 def estimateFlowcellTimeRemaining(flowcell):
     estimate_mid = estimateFlowcellDuration(flowcell)
-    
+
     # offset for how long we've been running
     running_time = datetime.now() - flowcell.run_date
     estimate_mid -= running_time
     # offset for how long we've been running
     running_time = datetime.now() - flowcell.run_date
     estimate_mid -= running_time
@@ -329,9 +335,9 @@ def roundToDays(estimate):
     estimate_low = timedelta(estimate.days, 0)
     # floor estimate_mid and add a day
     estimate_high = timedelta(estimate.days+1, 0)
     estimate_low = timedelta(estimate.days, 0)
     # floor estimate_mid and add a day
     estimate_high = timedelta(estimate.days+1, 0)
-    
+
     return (estimate_low, estimate_high)
     return (estimate_low, estimate_high)
-    
+
 
 def makeUserLaneMap(flowcell):
     """
 
 def makeUserLaneMap(flowcell):
     """
@@ -349,26 +355,26 @@ def makeUserLaneMap(flowcell):
 
 def getUsersForFlowcell(flowcell):
     users = set()
 
 def getUsersForFlowcell(flowcell):
     users = set()
-    
+
     for lane in flowcell.lane_set.all():
         for affiliation in lane.library.affiliations.all():
             for user in affiliation.users.all():
                 users.add(user)
     for lane in flowcell.lane_set.all():
         for affiliation in lane.library.affiliations.all():
             for user in affiliation.users.all():
                 users.add(user)
-                
+
     return users
     return users
-    
+
 def makeUserLibraryMap(libraries):
     """
     Given an interable set of libraries return a mapping or
     users interested in those libraries.
     """
     users = {}
 def makeUserLibraryMap(libraries):
     """
     Given an interable set of libraries return a mapping or
     users interested in those libraries.
     """
     users = {}
-    
+
     for library in libraries:
         for affiliation in library.affiliations.all():
             for user in affiliation.users.all():
                 users.setdefault(user,[]).append(library)
     for library in libraries:
         for affiliation in library.affiliations.all():
             for user in affiliation.users.all():
                 users.setdefault(user,[]).append(library)
-                
+
     return users
 
 def makeAffiliationLaneMap(flowcell):
     return users
 
 def makeAffiliationLaneMap(flowcell):
index 1afd74e1f9299e836c81d175f29a3e679c173662..e6ad6e314f28d3bd8e31ba8e7c2954e3f8b9eeaa 100644 (file)
@@ -1,5 +1,5 @@
 [
 [
-    {"pk": 5, "model": "auth.user", 
+    {"pk": 5, "model": "auth.user",
    "fields": {
        "username": "test",
        "first_name": "",
    "fields": {
        "username": "test",
        "first_name": "",
@@ -18,7 +18,7 @@
    {"pk": 5, "model": "samples.htsuser",
     "fields" : {}
    },
    {"pk": 5, "model": "samples.htsuser",
     "fields" : {}
    },
-   {"pk": 6, "model": "auth.user", 
+   {"pk": 6, "model": "auth.user",
    "fields": {
        "username": "admintest",
        "first_name": "",
    "fields": {
        "username": "admintest",
        "first_name": "",
@@ -37,7 +37,7 @@
    {"pk": 6, "model": "samples.htsuser",
     "fields" : {}
    },
    {"pk": 6, "model": "samples.htsuser",
     "fields" : {}
    },
-   {"pk": 7, "model": "auth.user", 
+   {"pk": 7, "model": "auth.user",
    "fields": {
        "username": "supertest",
        "first_name": "",
    "fields": {
        "username": "supertest",
        "first_name": "",
          "email": "pk5@example.com"
          }
      },
          "email": "pk5@example.com"
          }
      },
- {"pk": 1, "model": "experiments.clusterstation", "fields": {"name": "old"}}, 
- {"pk": 2, "model": "experiments.clusterstation", "fields": {"name": "loaner"}}, 
- {"pk": 3, "model": "experiments.clusterstation", "fields": {"name": "new"}}, 
- {"pk": 1, "model": "experiments.sequencer", "fields": {"name": "Rotifer (HWI-EAS229)"}}, 
- {"pk": 2, "model": "experiments.sequencer", "fields": {"name": "Tardigrade"}}, 
- {"pk": 3, "model": "experiments.sequencer", "fields": {"name": "Sequenced somewhere else."}}, 
- {"pk": 153, "model": "experiments.flowcell", 
+ {"pk": 1, "model": "experiments.clusterstation", "fields": {"name": "old"}},
+ {"pk": 2, "model": "experiments.clusterstation", "fields": {"name": "loaner"}},
+ {"pk": 3, "model": "experiments.clusterstation", "fields": {"name": "new"}},
+ {"pk": 1, "model": "experiments.sequencer", "fields": {"name": "Rotifer (HWI-EAS229)"}},
+ {"pk": 2, "model": "experiments.sequencer", "fields": {"name": "Tardigrade"}},
+ {"pk": 3, "model": "experiments.sequencer", "fields": {"name": "Sequenced somewhere else."}},
+ {"pk": 153, "model": "experiments.flowcell",
   "fields": {
   "fields": {
-      "paired_end": true, 
-      "run_date": "2007-09-27 22:12:13", 
-      "read_length": 36, 
+      "paired_end": true,
+      "run_date": "2007-09-27 22:12:13",
+      "read_length": 36,
       "notes": "",
       "advanced_run": false,
       "control_lane": 2,
       "notes": "",
       "advanced_run": false,
       "control_lane": 2,
       "sequencer": 2,
       "flowcell_id": "FC12150"
       }
       "sequencer": 2,
       "flowcell_id": "FC12150"
       }
-  }, 
+  },
   {"pk": 1193, "model": "experiments.lane",
    "fields": {
        "comment": "No change in cluster numbers, despite slight increase in pM",
   {"pk": 1193, "model": "experiments.lane",
    "fields": {
        "comment": "No change in cluster numbers, despite slight increase in pM",
        "lane_number": 1,
        "pM": "8"
        }
        "lane_number": 1,
        "pM": "8"
        }
-   }, 
+   },
    {"pk": 1192, "model": "experiments.lane",
     "fields": {
         "comment": "Other library",
    {"pk": 1192, "model": "experiments.lane",
     "fields": {
         "comment": "Other library",
         "lane_number": 1,
         "pM": "7"
         }
         "lane_number": 1,
         "pM": "7"
         }
-    }, 
+    },
 
 
-  {"pk": "10981", "model": "samples.library", 
+  {"pk": "10981", "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 400, 
-            "library_name": "Paired End Multiplexed Sp-BAC", 
-            "creation_date": "2009-07-21", 
-            "cell_line": 1, 
-            "library_species": 10, 
-            "library_type": null, 
-            "made_by": "Igor", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 400,
+            "library_name": "Paired End Multiplexed Sp-BAC",
+            "creation_date": "2009-07-21",
+            "cell_line": 1,
+            "library_species": 10,
+            "library_type": null,
+            "made_by": "Igor",
             "affiliations": [
                 1,2
             "affiliations": [
                 1,2
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "Done", 
-            "tags": [], 
-            "made_for": "Andy Cameron", 
-            "amplified_from_sample": null, 
-            "notes": "Combined 10957-10968", 
-            "undiluted_concentration": "5.1", 
-            "successful_pM": null, 
-            "experiment_type": 10, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "Done",
+            "tags": [],
+            "made_for": "Andy Cameron",
+            "amplified_from_sample": null,
+            "notes": "Combined 10957-10968",
+            "undiluted_concentration": "5.1",
+            "successful_pM": null,
+            "experiment_type": 10,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1194, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1194, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 2,
        "pM": "7"
        }
        "lane_number": 2,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11016", 
-        "model": "samples.library", 
+        "pk": "11016",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 325, 
-            "library_name": "Paired End Pfl #3 MP 7/24/9 a", 
-            "creation_date": "2009-08-06", 
-            "cell_line": 1, 
-            "library_species": 2, 
-            "library_type": 2, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 325,
+            "library_name": "Paired End Pfl #3 MP 7/24/9 a",
+            "creation_date": "2009-08-06",
+            "cell_line": 1,
+            "library_species": 2,
+            "library_type": 2,
+            "made_by": "Lorian",
             "affiliations": [
                 2
             "affiliations": [
                 2
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11006, 
-            "notes": "7/31/2009 16:08:22\tColor: Blue", 
-            "undiluted_concentration": "35.5", 
-            "successful_pM": null, 
-            "experiment_type": 8, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11006,
+            "notes": "7/31/2009 16:08:22\tColor: Blue",
+            "undiluted_concentration": "35.5",
+            "successful_pM": null,
+            "experiment_type": 8,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1195, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1195, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 3,
        "pM": "7"
        }
        "lane_number": 3,
        "pM": "7"
        }
-   },  
+   },
     {
     {
-        "pk": "SL039", 
-        "model": "samples.library", 
+        "pk": "SL039",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 99 GM12892", 
-            "creation_date": "2009-08-25", 
-            "cell_line": 1, 
-            "library_species": 8, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 99 GM12892",
+            "creation_date": "2009-08-25",
+            "cell_line": 1,
+            "library_species": 8,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 3,4
             "affiliations": [
                 3,4
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "fragment size = 300 bp, Amicon filtered\r\nnanodrop: 56.3", 
-            "undiluted_concentration": "28.7", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "fragment size = 300 bp, Amicon filtered\r\nnanodrop: 56.3",
+            "undiluted_concentration": "28.7",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1196, "model": "experiments.lane",
    "fields": {
        "comment": "This lane's library had the second lowest concentration of all the libraries built at the same time (2.05ng/ul)",
   {"pk": 1196, "model": "experiments.lane",
    "fields": {
        "comment": "This lane's library had the second lowest concentration of all the libraries built at the same time (2.05ng/ul)",
        "lane_number": 4,
        "pM": "7"
        }
        "lane_number": 4,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11060", 
-        "model": "samples.library", 
+        "pk": "11060",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 100 VC_CN_4_M_MBB1185_s1", 
-            "creation_date": "2009-09-01", 
-            "cell_line": 1, 
-            "library_species": 8, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 100 VC_CN_4_M_MBB1185_s1",
+            "creation_date": "2009-09-01",
+            "cell_line": 1,
+            "library_species": 8,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 5
             "affiliations": [
                 5
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 24.2", 
-            "undiluted_concentration": "2.05", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 24.2",
+            "undiluted_concentration": "2.05",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1197, "model": "experiments.lane",
    "fields": {
        "comment": "stuff",
   {"pk": 1197, "model": "experiments.lane",
    "fields": {
        "comment": "stuff",
        "pM": "7",
        "status": 2
        }
        "pM": "7",
        "status": 2
        }
-   }, 
+   },
     {
     {
-        "pk": "11061", 
-        "model": "samples.library", 
+        "pk": "11061",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 101 VC_CN_4_M_MBB1185_s2", 
-            "creation_date": "2009-09-01", 
-            "cell_line": 1, 
-            "library_species": 8, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 101 VC_CN_4_M_MBB1185_s2",
+            "creation_date": "2009-09-01",
+            "cell_line": 1,
+            "library_species": 8,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 2,4
             "affiliations": [
                 2,4
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 33.1", 
-            "undiluted_concentration": "12.9", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 33.1",
+            "undiluted_concentration": "12.9",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1198, "model": "experiments.lane",
    "fields": {
        "comment": "This lane's library had the lowest concentration of all the libraries built at the same time (1.2ng/ul)",
   {"pk": 1198, "model": "experiments.lane",
    "fields": {
        "comment": "This lane's library had the lowest concentration of all the libraries built at the same time (1.2ng/ul)",
        "pM": "7",
        "status": 0
        }
        "pM": "7",
        "status": 0
        }
-   }, 
+   },
     {
     {
-        "pk": "11062", 
-        "model": "samples.library", 
+        "pk": "11062",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 102 VC_AU_8_M_MBB4721_s1", 
-            "creation_date": "2009-09-01", 
-            "cell_line": 1, 
-            "library_species": 8, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 102 VC_AU_8_M_MBB4721_s1",
+            "creation_date": "2009-09-01",
+            "cell_line": 1,
+            "library_species": 8,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 4,5
             "affiliations": [
                 4,5
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 13.9", 
-            "undiluted_concentration": "1.2", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 13.9",
+            "undiluted_concentration": "1.2",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1199, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1199, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 7,
        "pM": "7"
        }
        "lane_number": 7,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11063", 
-        "model": "samples.library", 
+        "pk": "11063",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 103 VC_AU_8_M_MBB4721_s2", 
-            "creation_date": "2009-09-01", 
-            "cell_line": 1, 
-            "library_species": 8, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 103 VC_AU_8_M_MBB4721_s2",
+            "creation_date": "2009-09-01",
+            "cell_line": 1,
+            "library_species": 8,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 1,3
             "affiliations": [
                 1,3
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 46.9", 
-            "undiluted_concentration": "24.5", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 46.9",
+            "undiluted_concentration": "24.5",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1200, "model": "experiments.lane",
    "fields": {
        "comment": "This lane's library had the third lowest concentration of all the libraries built at the same time (5.21ng/ul), but gave perfectly normal cluster numbers",
   {"pk": 1200, "model": "experiments.lane",
    "fields": {
        "comment": "This lane's library had the third lowest concentration of all the libraries built at the same time (5.21ng/ul), but gave perfectly normal cluster numbers",
        }
    },
     {
        }
    },
     {
-        "pk": "11064", 
-        "model": "samples.library", 
+        "pk": "11064",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 104 VC_CN_7_M_MBB4898_s1", 
-            "creation_date": "2009-09-01", 
-            "cell_line": 1, 
-            "library_species": 8, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 104 VC_CN_7_M_MBB4898_s1",
+            "creation_date": "2009-09-01",
+            "cell_line": 1,
+            "library_species": 8,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 3,5
             "affiliations": [
                 3,5
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 20.4", 
-            "undiluted_concentration": "5.21", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 20.4",
+            "undiluted_concentration": "5.21",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
     },
             "antibody": null
         }
     },
-   {"pk": 152, "model": "experiments.flowcell", 
+   {"pk": 152, "model": "experiments.flowcell",
    "fields": {
        "paired_end": false,
        "run_date": "2009-09-10 18:30:15",
    "fields": {
        "paired_end": false,
        "run_date": "2009-09-10 18:30:15",
        "sequencer": 1,
        "flowcell_id": "42JTNAAXX"
        }
        "sequencer": 1,
        "flowcell_id": "42JTNAAXX"
        }
-   }, 
+   },
   {"pk": 1185, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1185, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
        }
    },
     {
-        "pk": "11035", 
-        "model": "samples.library", 
+        "pk": "11035",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 95 Gilberto_d3_control_LTA", 
-            "creation_date": "2009-08-25", 
-            "cell_line": 1, 
-            "library_species": 9, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 95 Gilberto_d3_control_LTA",
+            "creation_date": "2009-08-25",
+            "cell_line": 1,
+            "library_species": 9,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 3
             "affiliations": [
                 3
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "fragment size = 300 bp, Amicon filtered\r\nnanodrop: 67.1", 
-            "undiluted_concentration": "28.5", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "fragment size = 300 bp, Amicon filtered\r\nnanodrop: 67.1",
+            "undiluted_concentration": "28.5",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1186, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1186, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
        }
    },
     {
-        "pk": "11037", 
-        "model": "samples.library", 
+        "pk": "11037",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 97 Kuntz_PDHT", 
-            "creation_date": "2009-08-25", 
-            "cell_line": 1, 
-            "library_species": 3, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 97 Kuntz_PDHT",
+            "creation_date": "2009-08-25",
+            "cell_line": 1,
+            "library_species": 3,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 4
             "affiliations": [
                 4
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "fragment size = 300 bp, Amicon filtered\r\nnanodrop: 52.7", 
-            "undiluted_concentration": "25.5", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "fragment size = 300 bp, Amicon filtered\r\nnanodrop: 52.7",
+            "undiluted_concentration": "25.5",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1187, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1187, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
        }
    },
     {
-        "pk": "11045", 
-        "model": "samples.library", 
+        "pk": "11045",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 250, 
-            "library_name": "FLDN1 8/3/9 anti-AcH3 chip B6 a", 
-            "creation_date": "2009-08-26", 
-            "cell_line": null, 
-            "library_species": 9, 
-            "library_type": 1, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 250,
+            "library_name": "FLDN1 8/3/9 anti-AcH3 chip B6 a",
+            "creation_date": "2009-08-26",
+            "cell_line": null,
+            "library_species": 9,
+            "library_type": 1,
+            "made_by": "Lorian",
             "affiliations": [
                 5
             "affiliations": [
                 5
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "2A", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11044, 
-            "notes": "8/21/2009 11:57:54\tColor: Yellow", 
-            "undiluted_concentration": "20.5", 
-            "successful_pM": null, 
-            "experiment_type": 2, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "2A",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 12044,
+            "notes": "8/21/2009 11:57:54\tColor: Yellow",
+            "undiluted_concentration": "20.5",
+            "successful_pM": null,
+            "experiment_type": 2,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1188, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1188, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "pM": "7"}
    },
     {
        "pM": "7"}
    },
     {
-        "pk": "11046", 
-        "model": "samples.library", 
+        "pk": "11046",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 250, 
-            "library_name": "FLDN1 7/8/9 anti-DiMeH3K4 chip B6 a", 
-            "creation_date": "2009-08-26", 
-            "cell_line": null, 
-            "library_species": 9, 
-            "library_type": 1, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 250,
+            "library_name": "FLDN1 7/8/9 anti-DiMeH3K4 chip B6 a",
+            "creation_date": "2009-08-26",
+            "cell_line": null,
+            "library_species": 9,
+            "library_type": 1,
+            "made_by": "Lorian",
             "affiliations": [
                 6
             "affiliations": [
                 6
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "2A", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11045, 
-            "notes": "8/21/2009 11:57:54\tColor: Blue", 
-            "undiluted_concentration": "23.9", 
-            "successful_pM": null, 
-            "experiment_type": 2, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "2A",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11045,
+            "notes": "8/21/2009 11:57:54\tColor: Blue",
+            "undiluted_concentration": "23.9",
+            "successful_pM": null,
+            "experiment_type": 2,
             "antibody": null
         }
             "antibody": null
         }
-    },   
+    },
   {"pk": 1189, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1189, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 5,
        "pM": "7"
        }
        "lane_number": 5,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11054", 
-        "model": "samples.library", 
+        "pk": "11054",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 225, 
-            "library_name": "HNDHT HLH hnd-1 strain HT115 fed anti-hlh-1 2% fix plate a", 
-            "creation_date": "2009-08-31", 
-            "cell_line": null, 
-            "library_species": 3, 
-            "library_type": 1, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 225,
+            "library_name": "HNDHT HLH hnd-1 strain HT115 fed anti-hlh-1 2% fix plate a",
+            "creation_date": "2009-08-31",
+            "cell_line": null,
+            "library_species": 3,
+            "library_type": 1,
+            "made_by": "Lorian",
             "affiliations": [
                 1
             "affiliations": [
                 1
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11051, 
-            "notes": "8/26/2009 14:46:56\tColor: Purple", 
-            "undiluted_concentration": "1.47", 
-            "successful_pM": null, 
-            "experiment_type": 2, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11051,
+            "notes": "8/26/2009 14:46:56\tColor: Purple",
+            "undiluted_concentration": "1.47",
+            "successful_pM": null,
+            "experiment_type": 2,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1190, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1190, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
        }
    },
     {
-        "pk": "11056", 
-        "model": "samples.library", 
+        "pk": "11056",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 225, 
-            "library_name": "HNDM3 HLH hnd-1 strain mex-3 fed anti-hlh-1 2% fix plate a", 
-            "creation_date": "2009-08-31", 
-            "cell_line": null, 
-            "library_species": 3, 
-            "library_type": 1, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 225,
+            "library_name": "HNDM3 HLH hnd-1 strain mex-3 fed anti-hlh-1 2% fix plate a",
+            "creation_date": "2009-08-31",
+            "cell_line": null,
+            "library_species": 3,
+            "library_type": 1,
+            "made_by": "Lorian",
             "affiliations": [
                 2
             "affiliations": [
                 2
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11053, 
-            "notes": "8/26/2009 14:46:56\tColor: Black", 
-            "undiluted_concentration": "1.42", 
-            "successful_pM": null, 
-            "experiment_type": 2, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11053,
+            "notes": "8/26/2009 14:46:56\tColor: Black",
+            "undiluted_concentration": "1.42",
+            "successful_pM": null,
+            "experiment_type": 2,
             "antibody": null
         }
             "antibody": null
         }
-    },     
+    },
   {"pk": 1191, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1191, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 7,
        "pM": "7"
        }
        "lane_number": 7,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11057", 
-        "model": "samples.library", 
+        "pk": "11057",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 225, 
-            "library_name": "HNDM3 4H8 hnd-1 strain mex-3 fed 4H8 2% fix plate a", 
-            "creation_date": "2009-08-31", 
-            "cell_line": null, 
-            "library_species": 3, 
-            "library_type": 1, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 225,
+            "library_name": "HNDM3 4H8 hnd-1 strain mex-3 fed 4H8 2% fix plate a",
+            "creation_date": "2009-08-31",
+            "cell_line": null,
+            "library_species": 3,
+            "library_type": 1,
+            "made_by": "Lorian",
             "affiliations": [
                 3
             "affiliations": [
                 3
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11054, 
-            "notes": "8/26/2009 14:46:56\tColor: Orange.", 
-            "undiluted_concentration": "1.3", 
-            "successful_pM": null, 
-            "experiment_type": 2, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11054,
+            "notes": "8/26/2009 14:46:56\tColor: Orange.",
+            "undiluted_concentration": "1.3",
+            "successful_pM": null,
+            "experiment_type": 2,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1192, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1192, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 8,
        "pM": "7"
        }
        "lane_number": 8,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11065", 
-        "model": "samples.library", 
+        "pk": "11065",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 105 Kuntz PDM3", 
-            "creation_date": "2009-09-01", 
-            "cell_line": 1, 
-            "library_species": 3, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 105 Kuntz PDM3",
+            "creation_date": "2009-09-01",
+            "cell_line": 1,
+            "library_species": 3,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 4
             "affiliations": [
                 4
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 30.5", 
-            "undiluted_concentration": "2.47", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "300 bp gel fragment, Amicon filtered\r\nnanodrop: 30.5",
+            "undiluted_concentration": "2.47",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 151, "model": "experiments.flowcell",
    "fields": {
        "paired_end": false,
   {"pk": 151, "model": "experiments.flowcell",
    "fields": {
        "paired_end": false,
        "lane_number": 1,
        "pM": "7"
        }
        "lane_number": 1,
        "pM": "7"
        }
-   }, 
+   },
   {"pk": 1178, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1178, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 2,
        "pM": "7"
        }
        "lane_number": 2,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11036", 
-        "model": "samples.library", 
+        "pk": "11036",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 96 Kuntz_PDE1", 
-            "creation_date": "2009-08-25", 
-            "cell_line": 1, 
-            "library_species": 3, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 96 Kuntz_PDE1",
+            "creation_date": "2009-08-25",
+            "cell_line": 1,
+            "library_species": 3,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 5
             "affiliations": [
                 5
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "fragment size = 300 bp", 
-            "undiluted_concentration": "30.6", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "fragment size = 300 bp",
+            "undiluted_concentration": "30.6",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
     {
     {
-        "pk": "11034", 
-        "model": "samples.library", 
+        "pk": "11034",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired ends 94 Gilberto_d3_denerv_LTA", 
-            "creation_date": "2009-08-25", 
-            "cell_line": 1, 
-            "library_species": 9, 
-            "library_type": 2, 
-            "made_by": "Brian Williams", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired ends 94 Gilberto_d3_denerv_LTA",
+            "creation_date": "2009-08-25",
+            "cell_line": 1,
+            "library_species": 9,
+            "library_type": 2,
+            "made_by": "Brian Williams",
             "affiliations": [
                 1
             "affiliations": [
                 1
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "Brian Williams", 
-            "amplified_from_sample": null, 
-            "notes": "fragment size = 300 bp", 
-            "undiluted_concentration": "27", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "Brian Williams",
+            "amplified_from_sample": null,
+            "notes": "fragment size = 300 bp",
+            "undiluted_concentration": "27",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
     {
     {
-        "pk": "11044", 
-        "model": "samples.library", 
+        "pk": "12044",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 225, 
-            "library_name": "p300 60h C2 FA KF 12/22/8 a", 
-            "creation_date": "2009-08-26", 
-            "cell_line": null, 
-            "library_species": 9, 
-            "library_type": 1, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 225,
+            "library_name": "Pooled Indexed Test",
+            "creation_date": "2009-08-26",
+            "cell_line": null,
+            "library_species": 9,
+            "library_type": 5,
+            "multiplex_id": "1,2,3",
+            "made_by": "Lorian",
             "affiliations": [
                 2
             "affiliations": [
                 2
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "2A", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11043, 
-            "notes": "8/21/2009 11:57:54\tColor: Orange", 
-            "undiluted_concentration": "22.4", 
-            "successful_pM": null, 
-            "experiment_type": 2, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "2A",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11043,
+            "notes": "8/21/2009 11:57:54\tColor: Orange",
+            "undiluted_concentration": "22.4",
+            "successful_pM": null,
+            "experiment_type": 2,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
+    {
+        "pk": "11045",
+        "model": "samples.library",
+        "fields": {
+            "ten_nM_dilution": false,
+            "gel_cut_size": 225,
+            "library_name": "Simple Indexed Test",
+            "creation_date": "2009-08-26",
+            "cell_line": null,
+            "library_species": 9,
+            "library_type": 5,
+            "multiplex_id": "1",
+            "made_by": "Lorian",
+            "affiliations": [
+                2
+            ],
+            "replicate": 2,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "2A",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11043,
+            "notes": "8/21/2009 11:57:54\tColor: Orange",
+            "undiluted_concentration": "22.4",
+            "successful_pM": null,
+            "experiment_type": 2,
+            "antibody": null
+        }
+    },
   {"pk": 1179,
    "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1179,
    "model": "experiments.lane",
    "fields": {
        "comment": "",
-       "library": "11044",
+       "library": "12044",
+       "cluster_estimate": 196000,
+       "flowcell": 151,
+       "lane_number": 3,
+       "pM": "7"
+       }
+   },
+  {"pk": 1279,
+   "model": "experiments.lane",
+   "fields": {
+       "comment": "",
+       "library": "11045",
        "cluster_estimate": 196000,
        "flowcell": 151,
        "lane_number": 3,
        "pM": "7"
        }
        "cluster_estimate": 196000,
        "flowcell": 151,
        "lane_number": 3,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11044", 
-        "model": "samples.library", 
+        "pk": "11044",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 225, 
-            "library_name": "p300 60h C2 FA KF 12/22/8 a", 
-            "creation_date": "2009-08-26", 
-            "cell_line": null, 
-            "library_species": 9, 
-            "library_type": 1, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 225,
+            "library_name": "p300 60h C2 FA KF 12/22/8 a",
+            "creation_date": "2009-08-26",
+            "cell_line": null,
+            "library_species": 9,
+            "library_type": 1,
+            "made_by": "Lorian",
             "affiliations": [
                 3
             "affiliations": [
                 3
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "2A", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11043, 
-            "notes": "8/21/2009 11:57:54\tColor: Orange.", 
-            "undiluted_concentration": "22.4", 
-            "successful_pM": null, 
-            "experiment_type": 2, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "2A",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11043,
+            "notes": "8/21/2009 11:57:54\tColor: Orange.",
+            "undiluted_concentration": "22.4",
+            "successful_pM": null,
+            "experiment_type": 2,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1180, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1180, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 4,
        "pM": "7"
        }
        "lane_number": 4,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11047", 
-        "model": "samples.library", 
+        "pk": "11047",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 250, 
-            "library_name": "FLDN1 7/8/9 anti-TriMeH3K27 chip B6 a", 
-            "creation_date": "2009-08-26", 
-            "cell_line": null, 
-            "library_species": 9, 
-            "library_type": 1, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 250,
+            "library_name": "FLDN1 7/8/9 anti-TriMeH3K27 chip B6 a",
+            "creation_date": "2009-08-26",
+            "cell_line": null,
+            "library_species": 9,
+            "library_type": 1,
+            "made_by": "Lorian",
             "affiliations": [
                 4
             "affiliations": [
                 4
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "2A", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11046, 
-            "notes": "8/21/2009 11:57:54\tColor: Green", 
-            "undiluted_concentration": "24.9", 
-            "successful_pM": null, 
-            "experiment_type": 2, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "2A",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11046,
+            "notes": "8/21/2009 11:57:54\tColor: Green",
+            "undiluted_concentration": "24.9",
+            "successful_pM": null,
+            "experiment_type": 2,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1181, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1181, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 5,
        "pM": "7"
        }
        "lane_number": 5,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11055", 
-        "model": "samples.library", 
+        "pk": "11055",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 225, 
-            "library_name": "HNDHT 4H8 hnd-1 strain HT115 fed 4H8 2% fix plate a", 
-            "creation_date": "2009-08-31", 
-            "cell_line": null, 
-            "library_species": 3, 
-            "library_type": 1, 
-            "made_by": "Lorian", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 225,
+            "library_name": "HNDHT 4H8 hnd-1 strain HT115 fed 4H8 2% fix plate a",
+            "creation_date": "2009-08-31",
+            "cell_line": null,
+            "library_species": 3,
+            "library_type": 1,
+            "made_by": "Lorian",
             "affiliations": [
                 5
             "affiliations": [
                 5
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "1Aa", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": 11052, 
-            "notes": "8/26/2009 14:46:56\tColor: White.", 
-            "undiluted_concentration": "2.17", 
-            "successful_pM": null, 
-            "experiment_type": 2, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "1Aa",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": 11052,
+            "notes": "8/26/2009 14:46:56\tColor: White.",
+            "undiluted_concentration": "2.17",
+            "successful_pM": null,
+            "experiment_type": 2,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1182, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1182, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 6,
        "pM": "7"
        }
        "lane_number": 6,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11067", 
-        "model": "samples.library", 
+        "pk": "11067",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 325, 
-            "library_name": "Paired End SP-BAC Barcoding test 250-300 bp", 
-            "creation_date": "2009-09-03", 
-            "cell_line": 1, 
-            "library_species": 10, 
-            "library_type": 2, 
-            "made_by": "Igor", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 325,
+            "library_name": "Paired End SP-BAC Barcoding test 250-300 bp",
+            "creation_date": "2009-09-03",
+            "cell_line": 1,
+            "library_species": 10,
+            "library_type": 2,
+            "made_by": "Igor",
             "affiliations": [
                 1
             "affiliations": [
                 1
-            ], 
-            "replicate": 1, 
-            "condition": 1, 
-            "hidden": false, 
-            "stopping_point": "Done", 
-            "tags": [], 
-            "made_for": "Andy Cameron", 
-            "amplified_from_sample": null, 
-            "notes": "12 SP BACs", 
-            "undiluted_concentration": "1.45", 
-            "successful_pM": null, 
-            "experiment_type": 8, 
+            ],
+            "replicate": 1,
+            "condition": 1,
+            "hidden": false,
+            "stopping_point": "Done",
+            "tags": [],
+            "made_for": "Andy Cameron",
+            "amplified_from_sample": null,
+            "notes": "12 SP BACs",
+            "undiluted_concentration": "1.45",
+            "successful_pM": null,
+            "experiment_type": 8,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1183, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1183, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 7,
        "pM": "7"
        }
        "lane_number": 7,
        "pM": "7"
        }
-   }, 
+   },
     {
     {
-        "pk": "11069", 
-        "model": "samples.library", 
+        "pk": "11069",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired End AG-3d-1 AG domain of floral meristem day 3, rep 1", 
-            "creation_date": "2009-09-02", 
-            "cell_line": null, 
-            "library_species": 6, 
-            "library_type": 2, 
-            "made_by": "Yuling Jiao", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired End AG-3d-1 AG domain of floral meristem day 3, rep 1",
+            "creation_date": "2009-09-02",
+            "cell_line": null,
+            "library_species": 6,
+            "library_type": 2,
+            "made_by": "Yuling Jiao",
             "affiliations": [
                 2
             "affiliations": [
                 2
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "Done", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": null, 
-            "notes": "nanodrop: Xng/ul.", 
-            "undiluted_concentration": "18.3", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "Done",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": null,
+            "notes": "nanodrop: Xng/ul.",
+            "undiluted_concentration": "18.3",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
             "antibody": null
         }
-    }, 
+    },
   {"pk": 1184, "model": "experiments.lane",
    "fields": {
        "comment": "",
   {"pk": 1184, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
        }
    },
     {
-        "pk": "11070", 
-        "model": "samples.library", 
+        "pk": "11070",
+        "model": "samples.library",
         "fields": {
         "fields": {
-            "ten_nM_dilution": false, 
-            "gel_cut_size": 300, 
-            "library_name": "Paired End AG-5d-1 AG domain of floral meristem day 5, rep 1", 
-            "creation_date": "2009-09-02", 
-            "cell_line": null, 
-            "library_species": 6, 
-            "library_type": 2, 
-            "made_by": "Yuling Jiao", 
+            "ten_nM_dilution": false,
+            "gel_cut_size": 300,
+            "library_name": "Paired End AG-5d-1 AG domain of floral meristem day 5, rep 1",
+            "creation_date": "2009-09-02",
+            "cell_line": null,
+            "library_species": 6,
+            "library_type": 2,
+            "made_by": "Yuling Jiao",
             "affiliations": [
                 3
             "affiliations": [
                 3
-            ], 
-            "replicate": 1, 
-            "condition": null, 
-            "hidden": false, 
-            "stopping_point": "Done", 
-            "tags": [], 
-            "made_for": "", 
-            "amplified_from_sample": null, 
-            "notes": "nanodrop: 40ng/ul\r\nCalibrated qbit with standards.\r\nMeasured 2ul library with qbit using HS kit.\r\n", 
-            "undiluted_concentration": "20.3", 
-            "successful_pM": null, 
-            "experiment_type": 4, 
+            ],
+            "replicate": 1,
+            "condition": null,
+            "hidden": false,
+            "stopping_point": "Done",
+            "tags": [],
+            "made_for": "",
+            "amplified_from_sample": null,
+            "notes": "nanodrop: 40ng/ul\r\nCalibrated qbit with standards.\r\nMeasured 2ul library with qbit using HS kit.\r\n",
+            "undiluted_concentration": "20.3",
+            "successful_pM": null,
+            "experiment_type": 4,
             "antibody": null
         }
      },
             "antibody": null
         }
      },
- {"pk": 200, "model": "experiments.flowcell", 
+ {"pk": 200, "model": "experiments.flowcell",
   "fields": {
   "fields": {
-      "paired_end": true, 
-      "run_date": "2007-09-27 22:12:13", 
-      "read_length": 36, 
+      "paired_end": true,
+      "run_date": "2007-09-27 22:12:13",
+      "read_length": 36,
       "notes": "",
       "advanced_run": false,
       "control_lane": 2,
       "notes": "",
       "advanced_run": false,
       "control_lane": 2,
index 664547bfb7e5d799fca4e4036eb6221762197af8..ef299e3676a02bbd087917d2bfc8654f3a391668 100644 (file)
@@ -19,7 +19,8 @@ from htsworkflow.frontend.auth import apidata
 
 from htsworkflow.pipelines.test.simulate_runfolder import TESTDATA_DIR
 
 
 from htsworkflow.pipelines.test.simulate_runfolder import TESTDATA_DIR
 
-LANE_SET = range(1,9)
+LANE_SET = range(1, 9)
+
 
 class ExperimentsTestCases(TestCase):
     fixtures = ['test_flowcells.json']
 
 class ExperimentsTestCases(TestCase):
     fixtures = ['test_flowcells.json']
@@ -36,14 +37,14 @@ class ExperimentsTestCases(TestCase):
         runxml = 'run_FC12150_2007-09-27.xml'
         shutil.copy(os.path.join(TESTDATA_DIR, runxml),
                     os.path.join(self.fc1_dir, runxml))
         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):
+        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,
             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,))
+                             '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'))
         self.fc2_dir = os.path.join(self.tempdir, '42JTNAAXX')
         os.mkdir(self.fc2_dir)
         os.mkdir(os.path.join(self.fc2_dir, 'C1-25'))
@@ -62,50 +63,69 @@ class ExperimentsTestCases(TestCase):
             fc_django = models.FlowCell.objects.get(flowcell_id=fc_id)
             self.failUnlessEqual(fc_dict['flowcell_id'], fc_id)
             self.failUnlessEqual(fc_django.flowcell_id, fc_id)
             fc_django = models.FlowCell.objects.get(flowcell_id=fc_id)
             self.failUnlessEqual(fc_dict['flowcell_id'], fc_id)
             self.failUnlessEqual(fc_django.flowcell_id, fc_id)
-            self.failUnlessEqual(fc_dict['sequencer'], fc_django.sequencer.name)
-            self.failUnlessEqual(fc_dict['read_length'], fc_django.read_length)
+            self.failUnlessEqual(fc_dict['sequencer'],
+                                 fc_django.sequencer.name)
+            self.failUnlessEqual(fc_dict['read_length'],
+                                 fc_django.read_length)
             self.failUnlessEqual(fc_dict['notes'], fc_django.notes)
             self.failUnlessEqual(fc_dict['notes'], fc_django.notes)
-            self.failUnlessEqual(fc_dict['cluster_station'], fc_django.cluster_station.name)
+            self.failUnlessEqual(fc_dict['cluster_station'],
+                                 fc_django.cluster_station.name)
 
             for lane in fc_django.lane_set.all():
 
             for lane in fc_django.lane_set.all():
-                lane_dict = fc_dict['lane_set'][lane.lane_number]
-                self.failUnlessEqual(lane_dict['cluster_estimate'], lane.cluster_estimate)
+                lane_contents = fc_dict['lane_set'][lane.lane_number]
+                lane_dict = multi_lane_to_dict(lane_contents)[lane.library_id]
+                self.failUnlessEqual(lane_dict['cluster_estimate'],
+                                     lane.cluster_estimate)
                 self.failUnlessEqual(lane_dict['comment'], lane.comment)
                 self.failUnlessEqual(lane_dict['comment'], lane.comment)
-                self.failUnlessEqual(lane_dict['flowcell'], lane.flowcell.flowcell_id)
-                self.failUnlessEqual(lane_dict['lane_number'], lane.lane_number)
-                self.failUnlessEqual(lane_dict['library_name'], lane.library.library_name)
+                self.failUnlessEqual(lane_dict['flowcell'],
+                                     lane.flowcell.flowcell_id)
+                self.failUnlessEqual(lane_dict['lane_number'],
+                                     lane.lane_number)
+                self.failUnlessEqual(lane_dict['library_name'],
+                                     lane.library.library_name)
                 self.failUnlessEqual(lane_dict['library_id'], lane.library.id)
                 self.failUnlessEqual(lane_dict['library_id'], lane.library.id)
-                self.failUnlessAlmostEqual(float(lane_dict['pM']), float(lane.pM))
+                self.failUnlessAlmostEqual(float(lane_dict['pM']),
+                                           float(lane.pM))
                 self.failUnlessEqual(lane_dict['library_species'],
                 self.failUnlessEqual(lane_dict['library_species'],
-                                     lane.library.library_species.scientific_name)
-                    
-            response = self.client.get('/experiments/config/%s/json' % (fc_id,), apidata)
+                     lane.library.library_species.scientific_name)
+
+            flowcell_url = '/experiments/config/%s/json'
+            response = self.client.get(flowcell_url % (fc_id,), apidata)
             # strptime isoformat string = '%Y-%m-%dT%H:%M:%S'
             fc_json = json.loads(response.content)
             self.failUnlessEqual(fc_json['flowcell_id'], fc_id)
             # strptime isoformat string = '%Y-%m-%dT%H:%M:%S'
             fc_json = json.loads(response.content)
             self.failUnlessEqual(fc_json['flowcell_id'], fc_id)
-            self.failUnlessEqual(fc_json['sequencer'], fc_django.sequencer.name)
+            self.failUnlessEqual(fc_json['sequencer'],
+                                 fc_django.sequencer.name)
             self.failUnlessEqual(fc_json['read_length'], fc_django.read_length)
             self.failUnlessEqual(fc_json['notes'], fc_django.notes)
             self.failUnlessEqual(fc_json['read_length'], fc_django.read_length)
             self.failUnlessEqual(fc_json['notes'], fc_django.notes)
-            self.failUnlessEqual(fc_json['cluster_station'], fc_django.cluster_station.name)
-
+            self.failUnlessEqual(fc_json['cluster_station'],
+                                 fc_django.cluster_station.name)
 
             for lane in fc_django.lane_set.all():
 
             for lane in fc_django.lane_set.all():
-                lane_dict = fc_json['lane_set'][unicode(lane.lane_number)]
-                self.failUnlessEqual(lane_dict['cluster_estimate'], lane.cluster_estimate)
+                lane_contents = fc_json['lane_set'][unicode(lane.lane_number)]
+                lane_dict = multi_lane_to_dict(lane_contents)[lane.library_id]
+
+                self.failUnlessEqual(lane_dict['cluster_estimate'],
+                                     lane.cluster_estimate)
                 self.failUnlessEqual(lane_dict['comment'], lane.comment)
                 self.failUnlessEqual(lane_dict['comment'], lane.comment)
-                self.failUnlessEqual(lane_dict['flowcell'], lane.flowcell.flowcell_id)
-                self.failUnlessEqual(lane_dict['lane_number'], lane.lane_number)
-                self.failUnlessEqual(lane_dict['library_name'], lane.library.library_name)
+                self.failUnlessEqual(lane_dict['flowcell'],
+                                     lane.flowcell.flowcell_id)
+                self.failUnlessEqual(lane_dict['lane_number'],
+                                     lane.lane_number)
+                self.failUnlessEqual(lane_dict['library_name'],
+                                     lane.library.library_name)
                 self.failUnlessEqual(lane_dict['library_id'], lane.library.id)
                 self.failUnlessEqual(lane_dict['library_id'], lane.library.id)
-                self.failUnlessAlmostEqual(float(lane_dict['pM']), float(lane.pM))
+                self.failUnlessAlmostEqual(float(lane_dict['pM']),
+                                           float(lane.pM))
                 self.failUnlessEqual(lane_dict['library_species'],
                 self.failUnlessEqual(lane_dict['library_species'],
-                                     lane.library.library_species.scientific_name)
+                     lane.library.library_species.scientific_name)
 
     def test_invalid_flowcell(self):
         """
         Make sure we get a 404 if we request an invalid flowcell ID
         """
 
     def test_invalid_flowcell(self):
         """
         Make sure we get a 404 if we request an invalid flowcell ID
         """
-        response = self.client.get('/experiments/config/nottheone/json', apidata)
+        flowcell_url = '/experiments/config/nottheone/json'
+        response = self.client.get(flowcell_url, apidata)
         self.failUnlessEqual(response.status_code, 404)
 
     def test_no_key(self):
         self.failUnlessEqual(response.status_code, 404)
 
     def test_no_key(self):
@@ -117,13 +137,16 @@ class ExperimentsTestCases(TestCase):
 
     def test_library_id(self):
         """
 
     def test_library_id(self):
         """
-        Library IDs should be flexible, so make sure we can retrive a non-numeric ID
+        Library IDs should be flexible, retrive a non-numeric ID
         """
         """
-        response = self.client.get('/experiments/config/FC12150/json', apidata)
+        flowcell_url = '/experiments/config/FC12150/json'
+        response = self.client.get(flowcell_url, apidata)
         self.failUnlessEqual(response.status_code, 200)
         flowcell = json.loads(response.content)
 
         self.failUnlessEqual(response.status_code, 200)
         flowcell = json.loads(response.content)
 
-        self.failUnlessEqual(flowcell['lane_set']['3']['library_id'], 'SL039')
+        lane_contents = flowcell['lane_set']['3']
+        lane_library = lane_contents[0]
+        self.failUnlessEqual(lane_library['library_id'], 'SL039')
 
         response = self.client.get('/samples/library/SL039/json', apidata)
         self.failUnlessEqual(response.status_code, 200)
 
         response = self.client.get('/samples/library/SL039/json', apidata)
         self.failUnlessEqual(response.status_code, 200)
@@ -137,21 +160,22 @@ class ExperimentsTestCases(TestCase):
 
         Library's have IDs, libraries also have primary keys,
         we eventually had enough libraries that the drop down combo box was too
 
         Library's have IDs, libraries also have primary keys,
         we eventually had enough libraries that the drop down combo box was too
-        hard to filter through, unfortnately we want a field that uses our library
-        id and not the internal primary key, and raw_id_field uses primary keys.
+        hard to filter through, unfortnately we want a field that uses our
+        library id and not the internal primary key, and raw_id_field uses
+        primary keys.
 
 
-        This tests to make sure that the value entered in the raw library id field matches
-        the library id looked up.
+        This tests to make sure that the value entered in the raw library id
+        field matches the library id looked up.
         """
         """
-        expected_ids = [u'10981',u'11016',u'SL039',u'11060',
-                        u'11061',u'11062',u'11063',u'11064']
+        expected_ids = [u'10981', u'11016', u'SL039', u'11060',
+                        u'11061', u'11062', u'11063', u'11064']
         self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5')
         response = self.client.get('/admin/experiments/flowcell/153/')
         soup = BeautifulSoup(response.content)
         self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5')
         response = self.client.get('/admin/experiments/flowcell/153/')
         soup = BeautifulSoup(response.content)
-        for i in range(0,8):
+        for i in range(0, 8):
             input_field = soup.find(id='id_lane_set-%d-library' % (i,))
             library_field = input_field.findNext('strong')
             input_field = soup.find(id='id_lane_set-%d-library' % (i,))
             library_field = input_field.findNext('strong')
-            library_id, library_name = library_field.string.split(':')
+            library_id, library_name = library_field.text.split(':')
             # strip leading '#' sign from name
             library_id = library_id[1:]
             self.failUnlessEqual(library_id, expected_ids[i])
             # strip leading '#' sign from name
             library_id = library_id[1:]
             self.failUnlessEqual(library_id, expected_ids[i])
@@ -174,8 +198,19 @@ class ExperimentsTestCases(TestCase):
         self.failUnlessEqual(fc_response.status_code, 200)
         fc_lane_response = self.client.get('/flowcell/30012AAXX/8/')
         self.failUnlessEqual(fc_lane_response.status_code, 200)
         self.failUnlessEqual(fc_response.status_code, 200)
         fc_lane_response = self.client.get('/flowcell/30012AAXX/8/')
         self.failUnlessEqual(fc_lane_response.status_code, 200)
-        
-        
+
+    def test_pooled_multiplex_id(self):
+        fc_dict = experiments.flowcell_information('42JU1AAXX')
+        lane_contents = fc_dict['lane_set'][3]
+        self.assertEqual(len(lane_contents), 2)
+        lane_dict = multi_lane_to_dict(lane_contents)
+
+        self.assertEqual(lane_dict['12044']['index_sequence'],
+                         {u'1': u'ATCACG',
+                          u'2': u'CGATGT',
+                          u'3': u'TTAGGC'})
+        self.assertEqual(lane_dict['11045']['index_sequence'],
+                         {u'1': u'ATCACG'})
 
     def test_lanes_for(self):
         """
 
     def test_lanes_for(self):
         """
@@ -185,24 +220,29 @@ class ExperimentsTestCases(TestCase):
         lanes = experiments.lanes_for(user)
         self.failUnlessEqual(len(lanes), 5)
 
         lanes = experiments.lanes_for(user)
         self.failUnlessEqual(len(lanes), 5)
 
-        response = self.client.get('/experiments/lanes_for/%s/json' % (user,), apidata)
+        flowcell_url = '/experiments/lanes_for/%s/json'
+        response = self.client.get(flowcell_url % (user,), apidata)
         lanes_json = json.loads(response.content)
         self.failUnlessEqual(len(lanes), len(lanes_json))
         for i in range(len(lanes)):
             self.failUnlessEqual(lanes[i]['comment'], lanes_json[i]['comment'])
         lanes_json = json.loads(response.content)
         self.failUnlessEqual(len(lanes), len(lanes_json))
         for i in range(len(lanes)):
             self.failUnlessEqual(lanes[i]['comment'], lanes_json[i]['comment'])
-            self.failUnlessEqual(lanes[i]['lane_number'], lanes_json[i]['lane_number'])
-            self.failUnlessEqual(lanes[i]['flowcell'], lanes_json[i]['flowcell'])
-            self.failUnlessEqual(lanes[i]['run_date'], lanes_json[i]['run_date'])
-            
+            self.failUnlessEqual(lanes[i]['lane_number'],
+                                 lanes_json[i]['lane_number'])
+            self.failUnlessEqual(lanes[i]['flowcell'],
+                                 lanes_json[i]['flowcell'])
+            self.failUnlessEqual(lanes[i]['run_date'],
+                                 lanes_json[i]['run_date'])
+
     def test_lanes_for_no_lanes(self):
         """
     def test_lanes_for_no_lanes(self):
         """
-        Do we get something meaningful back when the user isn't attached to anything?
+        What happens to user who haven't submitted anything
         """
         user = 'supertest'
         lanes = experiments.lanes_for(user)
         self.failUnlessEqual(len(lanes), 0)
 
         """
         user = 'supertest'
         lanes = experiments.lanes_for(user)
         self.failUnlessEqual(len(lanes), 0)
 
-        response = self.client.get('/experiments/lanes_for/%s/json' % (user,), apidata)
+        url = '/experiments/lanes_for/%s/json'
+        response = self.client.get(url % (user,), apidata)
         lanes_json = json.loads(response.content)
 
     def test_lanes_for_no_user(self):
         lanes_json = json.loads(response.content)
 
     def test_lanes_for_no_user(self):
@@ -212,22 +252,21 @@ class ExperimentsTestCases(TestCase):
         user = 'not a real user'
         self.failUnlessRaises(ObjectDoesNotExist, experiments.lanes_for, user)
 
         user = 'not a real user'
         self.failUnlessRaises(ObjectDoesNotExist, experiments.lanes_for, user)
 
-        response = self.client.get('/experiments/lanes_for/%s/json' % (user,), apidata)
+        url = '/experiments/lanes_for/%s/json'
+        response = self.client.get(url % (user,), apidata)
         self.failUnlessEqual(response.status_code, 404)
 
         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)
     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)
 
         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')
     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')
@@ -254,10 +293,9 @@ class ExperimentsTestCases(TestCase):
         lane_files = run.lane_files()
         self.failUnlessEqual(lane_files[4]['srf'], srf4)
 
         lane_files = run.lane_files()
         self.failUnlessEqual(lane_files[4]['srf'], srf4)
 
-        runxml= result_dict['FC12150/C1-37/run_FC12150_2007-09-27.xml']
+        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)
         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
 
     def test_read_result_file(self):
         """make sure we can return a result file
@@ -265,20 +303,21 @@ class ExperimentsTestCases(TestCase):
         flowcell_id = self.fc1_id
         flowcell = models.FlowCell.objects.get(flowcell_id=flowcell_id)
         flowcell.update_data_runs()
         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') 
+
+        #self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5')
 
         result_files = flowcell.datarun_set.all()[0].datafile_set.all()
         for f in result_files:
 
         result_files = flowcell.datarun_set.all()[0].datafile_set.all()
         for f in result_files:
-            url = '/experiments/file/%s' % ( f.random_key,)
+            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'
             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'])
             self.failUnlessEqual(mimetype, response['content-type'])
-        
+
+
 class TestFileType(TestCase):
     def test_file_type_unicode(self):
         file_type_objects = models.FileType.objects
 class TestFileType(TestCase):
     def test_file_type_unicode(self):
         file_type_objects = models.FileType.objects
@@ -286,7 +325,8 @@ class TestFileType(TestCase):
         file_type_object = file_type_objects.get(name=name)
         self.failUnlessEqual(u"<FileType: QSEQ tarfile>",
                              unicode(file_type_object))
         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
 class TestFileType(TestCase):
     def test_find_file_type(self):
         file_type_objects = models.FileType.objects
@@ -294,9 +334,9 @@ class TestFileType(TestCase):
                   'QSEQ tarfile', 7, 1),
                  ('woldlab_091005_HWUSI-EAS627_0010_42JT2AAXX_1.srf',
                   'SRF', 1, None),
                   '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_1_eland_extended.txt.bz2', 'ELAND Extended', 1, None),
                  ('s_7_eland_multi.txt.bz2', 'ELAND Multi', 7, None),
                  ('s_7_eland_multi.txt.bz2', 'ELAND Multi', 7, None),
-                 ('s_3_eland_result.txt.bz2','ELAND Result', 3, 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_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),
@@ -319,10 +359,10 @@ class TestFileType(TestCase):
                   'QSEQ tarfile', 7, 1),
                  ('foo/woldlab_091005_HWUSI-EAS627_0010_42JT2AAXX_1.srf',
                   'SRF', 1, None),
                   '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),
+                 ('../s_1_eland_extended.txt.bz2', 'ELAND Extended', 1, None),
                  ('/bleem/s_7_eland_multi.txt.bz2', 'ELAND Multi', 7, 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),
+                 ('/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),
                  ('/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),
@@ -335,9 +375,10 @@ class TestFileType(TestCase):
             result = models.find_file_type_metadata_from_filename(filename)
             self.failUnlessEqual(result['file_type'],
                                  file_type_objects.get(name=typename))
             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('lane', None), lane)
             self.failUnlessEqual(result.get('end', None), end)
             self.failUnlessEqual(result.get('end', None), end)
-                             
+
+
 class TestEmailNotify(TestCase):
     fixtures = ['test_flowcells.json']
 
 class TestEmailNotify(TestCase):
     fixtures = ['test_flowcells.json']
 
@@ -349,21 +390,22 @@ class TestEmailNotify(TestCase):
         self.client.login(username='test', password='BJOKL5kAj6aFZ6A5')
         response = self.client.get('/experiments/started/153/')
         self.failUnlessEqual(response.status_code, 302)
         self.client.login(username='test', password='BJOKL5kAj6aFZ6A5')
         response = self.client.get('/experiments/started/153/')
         self.failUnlessEqual(response.status_code, 302)
-        
+
     def test_started_email_logged_in_staff(self):
     def test_started_email_logged_in_staff(self):
-        self.client.login(username='admintest', password='BJOKL5kAj6aFZ6A5') 
+        self.client.login(username='admintest', password='BJOKL5kAj6aFZ6A5')
         response = self.client.get('/experiments/started/153/')
         self.failUnlessEqual(response.status_code, 200)
 
     def test_started_email_send(self):
         response = self.client.get('/experiments/started/153/')
         self.failUnlessEqual(response.status_code, 200)
 
     def test_started_email_send(self):
-        self.client.login(username='admintest', password='BJOKL5kAj6aFZ6A5') 
+        self.client.login(username='admintest', password='BJOKL5kAj6aFZ6A5')
         response = self.client.get('/experiments/started/153/')
         self.failUnlessEqual(response.status_code, 200)
         response = self.client.get('/experiments/started/153/')
         self.failUnlessEqual(response.status_code, 200)
-        
+
         self.failUnless('pk1@example.com' in response.content)
         self.failUnless('Lane #8 : (11064) Paired ends 104' in response.content)
 
         self.failUnless('pk1@example.com' in response.content)
         self.failUnless('Lane #8 : (11064) Paired ends 104' in response.content)
 
-        response = self.client.get('/experiments/started/153/', {'send':'1','bcc':'on'})
+        response = self.client.get('/experiments/started/153/',
+                                   {'send': '1','bcc': 'on'})
         self.failUnlessEqual(response.status_code, 200)
         self.failUnlessEqual(len(mail.outbox), 4)
         for m in mail.outbox:
         self.failUnlessEqual(response.status_code, 200)
         self.failUnlessEqual(len(mail.outbox), 4)
         for m in mail.outbox:
@@ -373,11 +415,16 @@ class TestEmailNotify(TestCase):
         """
         Can we navigate between the flowcell and email forms properly?
         """
         """
         Can we navigate between the flowcell and email forms properly?
         """
-        self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5') 
+        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 FC12150', response.content))
         # require that navigation back to the admin page exists
         response = self.client.get('/experiments/started/153/')
         self.failUnlessEqual(response.status_code, 200)
         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))
-        
+        flowcell_a_re = '<a href="/admin/experiments/flowcell/153/">[^<]+</a>'
+        self.failUnless(re.search(flowcell_a_re, response.content))
+
 
 
+def multi_lane_to_dict(lane):
+    """Convert a list of lane entries into a dictionary indexed by library ID
+    """
+    return dict(((x['library_id'], x) for x in lane))
index 4247c88b68d7998771a1adad4c04d28d24587d28..bcd77e2ca26e9db6d51ece2e41a33862ee92e686 100644 (file)
@@ -8,7 +8,9 @@ from django.db import models
 from django.utils.translation import ugettext_lazy as _
 from django.forms import TextInput, Textarea
 
 from django.utils.translation import ugettext_lazy as _
 from django.forms import TextInput, Textarea
 
-from htsworkflow.frontend.samples.models import Antibody, Cellline, Condition, ExperimentType, HTSUser, LibraryType, Species, Affiliation, Library, Tag
+from htsworkflow.frontend.samples.models import \
+     Antibody, Cellline, Condition, ExperimentType, HTSUser, \
+     LibraryType, MultiplexIndex, Species, Affiliation, Library, Tag
 from htsworkflow.frontend.experiments.models import Lane
 from htsworkflow.frontend.inventory.models import PrinterTemplate
 from htsworkflow.frontend.bcmagic.utils import print_zpl_socket
 from htsworkflow.frontend.experiments.models import Lane
 from htsworkflow.frontend.inventory.models import PrinterTemplate
 from htsworkflow.frontend.bcmagic.utils import print_zpl_socket
@@ -24,7 +26,7 @@ class AffiliationOptions(admin.ModelAdmin):
       }),
     )
 
       }),
     )
 
-    # some post 1.0.2 version of django has formfield_overrides 
+    # some post 1.0.2 version of django has formfield_overrides
     # which would replace this code with:
     # formfield_overrids = {
     #   models.ManyToMany: { 'widget': widgets.FilteredSelectMultiple }
     # which would replace this code with:
     # formfield_overrids = {
     #   models.ManyToMany: { 'widget': widgets.FilteredSelectMultiple }
@@ -76,7 +78,7 @@ class HTSUserCreationForm(UserCreationForm):
 class HTSUserChangeForm(UserChangeForm):
     class Meta:
         model = HTSUser
 class HTSUserChangeForm(UserChangeForm):
     class Meta:
         model = HTSUser
-        
+
 class HTSUserOptions(UserAdmin):
     form = HTSUserChangeForm
     add_form = HTSUserCreationForm
 class HTSUserOptions(UserAdmin):
     form = HTSUserChangeForm
     add_form = HTSUserCreationForm
@@ -89,14 +91,19 @@ class Library_Inline(admin.TabularInline):
   model = Library
 
 class LibraryTypeOptions(admin.ModelAdmin):
   model = Library
 
 class LibraryTypeOptions(admin.ModelAdmin):
+    list_display = ['name', 'is_paired_end', 'can_multiplex']
     model = LibraryType
 
     model = LibraryType
 
+class MultiplexIndexOptions(admin.ModelAdmin):
+    model = MultiplexIndex
+    list_display = ['adapter_type', 'multiplex_id', 'sequence']
+
 class LibraryOptions(admin.ModelAdmin):
     class Media:
         css = {
             "all": ("css/wide_account_number.css",)
             }
 class LibraryOptions(admin.ModelAdmin):
     class Media:
         css = {
             "all": ("css/wide_account_number.css",)
             }
-        
+
     date_hierarchy = "creation_date"
     save_as = True
     save_on_top = True
     date_hierarchy = "creation_date"
     save_as = True
     save_on_top = True
@@ -110,7 +117,7 @@ class LibraryOptions(admin.ModelAdmin):
     list_display = (
         'id',
         'library_name',
     list_display = (
         'id',
         'library_name',
-        'public',
+        'index_sequence_text',
         'affiliation',
         'undiluted_concentration',
         'gel_cut_size',
         'affiliation',
         'undiluted_concentration',
         'gel_cut_size',
@@ -119,9 +126,9 @@ class LibraryOptions(admin.ModelAdmin):
     list_filter = (
         'hidden',
         'affiliations',
     list_filter = (
         'hidden',
         'affiliations',
-        'library_species', 
-        'experiment_type', 
-        'made_by', 
+        'library_species',
+        'experiment_type',
+        'made_by',
         'cell_line',
         'stopping_point',)
     list_display_links = ('id', 'library_name',)
         'cell_line',
         'stopping_point',)
     list_display_links = ('id', 'library_name',)
@@ -129,7 +136,8 @@ class LibraryOptions(admin.ModelAdmin):
       (None, {
         'fields': (
           ('id','library_name','hidden'),
       (None, {
         'fields': (
           ('id','library_name','hidden'),
-          ('library_species', 'library_type', 'experiment_type'),
+          ('library_species', 'experiment_type'),
+          ('library_type', 'multiplex_id'),
           )
          }),
          ('Experiment Detail:', {
           )
          }),
          ('Experiment Detail:', {
@@ -140,12 +148,12 @@ class LibraryOptions(admin.ModelAdmin):
             'classes': ('collapse',),
             }),
          ('Creation Information:', {
             'classes': ('collapse',),
             }),
          ('Creation Information:', {
-             'fields' : (('made_by', 'creation_date', 'stopping_point'), 
-                         ('amplified_from_sample'), 
-                         ('gel_cut_size', 'insert_size', 
-                          'undiluted_concentration'), 
+             'fields' : (('made_by', 'creation_date', 'stopping_point'),
+                         ('amplified_from_sample'),
+                         ('gel_cut_size', 'insert_size',
+                          'undiluted_concentration'),
                          ('bioanalyzer_concentration','bioanalyzer_image_url'),
                          ('bioanalyzer_concentration','bioanalyzer_image_url'),
-                         ('bioanalyzer_summary'), 
+                         ('bioanalyzer_summary'),
                          ('notes'))
          }),
          ('Library/Project Affiliation:', {
                          ('notes'))
          }),
          ('Library/Project Affiliation:', {
@@ -156,45 +164,45 @@ class LibraryOptions(admin.ModelAdmin):
       LaneLibraryInline,
     ]
     actions = ['action_print_library_labels']
       LaneLibraryInline,
     ]
     actions = ['action_print_library_labels']
-    
+
     def action_print_library_labels(self, request, queryset):
         """
         Django action which prints labels for the selected set of labels from the
         Django Admin interface.
         """
     def action_print_library_labels(self, request, queryset):
         """
         Django action which prints labels for the selected set of labels from the
         Django Admin interface.
         """
-        
+
         #Probably should ask if the user really meant to print all selected
         # libraries if the count is above X. X=10 maybe?
         #Probably should ask if the user really meant to print all selected
         # libraries if the count is above X. X=10 maybe?
-        
+
         # Grab the library template
         #FIXME: Hardcoding library template name. Not a good idea... *sigh*.
         EVIL_HARDCODED_LIBRARY_TEMPLATE_NAME = "Library"
         # Grab the library template
         #FIXME: Hardcoding library template name. Not a good idea... *sigh*.
         EVIL_HARDCODED_LIBRARY_TEMPLATE_NAME = "Library"
-        
+
         try:
             template = PrinterTemplate.objects.get(item_type__name=EVIL_HARDCODED_LIBRARY_TEMPLATE_NAME)
         except PrinterTemplate.DoesNotExist:
             self.message_user(request, "Could not find a library template with ItemType.name of '%s'" % \
                               (EVIL_HARDCODED_LIBRARY_TEMPLATE_NAME))
             return
         try:
             template = PrinterTemplate.objects.get(item_type__name=EVIL_HARDCODED_LIBRARY_TEMPLATE_NAME)
         except PrinterTemplate.DoesNotExist:
             self.message_user(request, "Could not find a library template with ItemType.name of '%s'" % \
                               (EVIL_HARDCODED_LIBRARY_TEMPLATE_NAME))
             return
-        
+
         # ZPL Template
         t = Template(template.template)
         # ZPL Template
         t = Template(template.template)
-        
+
         zpl_list = []
         #Iterate over selected labels to print
         for library in queryset.all():
         zpl_list = []
         #Iterate over selected labels to print
         for library in queryset.all():
-            
+
             # Django Template Context
             c = Context({'library': library})
             # Django Template Context
             c = Context({'library': library})
-            
+
             # Send rendered template to the printer that the template
             #  object has been attached to in the database.
             zpl_list.append(t.render(c))
             # Send rendered template to the printer that the template
             #  object has been attached to in the database.
             zpl_list.append(t.render(c))
-        
+
         print_zpl_socket(zpl_list, host=template.printer.ip_address)
         print_zpl_socket(zpl_list, host=template.printer.ip_address)
-    
+
         self.message_user(request, "%s labels printed." % (len(queryset)))
         self.message_user(request, "%s labels printed." % (len(queryset)))
-                          
+
     action_print_library_labels.short_description = "Print Labels"
 
     def formfield_for_dbfield(self, db_field, **kwargs):
     action_print_library_labels.short_description = "Print Labels"
 
     def formfield_for_dbfield(self, db_field, **kwargs):
@@ -220,7 +228,7 @@ class SpeciesOptions(admin.ModelAdmin):
 
 class TagOptions(admin.ModelAdmin):
     list_display = ('tag_name', 'context')
 
 class TagOptions(admin.ModelAdmin):
     list_display = ('tag_name', 'context')
-    fieldsets = ( 
+    fieldsets = (
         (None, {
           'fields': ('tag_name', 'context')
           }),
         (None, {
           'fields': ('tag_name', 'context')
           }),
@@ -234,5 +242,6 @@ admin.site.register(Condition, ConditionOptions)
 admin.site.register(ExperimentType, ExperimentTypeOptions)
 #admin.site.register(HTSUser, HTSUserOptions)
 admin.site.register(LibraryType, LibraryTypeOptions)
 admin.site.register(ExperimentType, ExperimentTypeOptions)
 #admin.site.register(HTSUser, HTSUserOptions)
 admin.site.register(LibraryType, LibraryTypeOptions)
+admin.site.register(MultiplexIndex, MultiplexIndexOptions)
 admin.site.register(Species, SpeciesOptions)
 #admin.site.register(Tag, TagOptions)
 admin.site.register(Species, SpeciesOptions)
 #admin.site.register(Tag, TagOptions)
index 008867e0ce7e0c33050203a91affb4eba91a0d16..72cbaf855db13b09b7b28fb3dbe16e2adb3ab7e5 100644 (file)
@@ -4,7 +4,7 @@
      "pk": 1,
      "fields": {
         "cellline_name": "Unknown",
      "pk": 1,
      "fields": {
         "cellline_name": "Unknown",
-        "notes": "Unknown"    
+        "notes": "Unknown"
      }
   },
   {
      }
   },
   {
@@ -12,7 +12,7 @@
      "pk": 2,
      "fields": {
         "cellline_name": "C2C12 Exponential",
      "pk": 2,
      "fields": {
         "cellline_name": "C2C12 Exponential",
-        "notes": ""    
+        "notes": ""
      }
   },
   {
      }
   },
   {
      "pk": 3,
      "fields": {
         "cellline_name": "C2C12 60 hrs",
      "pk": 3,
      "fields": {
         "cellline_name": "C2C12 60 hrs",
-        "notes": "Unknown"    
+        "notes": "Unknown"
      }
   },
   {
      "model": "samples.LibraryType",
      "pk": 1,
      "fields": {
      }
   },
   {
      "model": "samples.LibraryType",
      "pk": 1,
      "fields": {
-        "name": "Single End"
+        "name": "Single End",
+        "can_multiplex": false,
+        "is_paired_end": false
      }
   },
   {
      "model": "samples.LibraryType",
      "pk": 2,
      "fields": {
      }
   },
   {
      "model": "samples.LibraryType",
      "pk": 2,
      "fields": {
-        "name": "Paired End"
+        "name": "Paired End",
+        "can_multiplex": false,
+        "is_paired_end": true
+     }
+  },
+  {
+     "model": "samples.LibraryType",
+     "pk": 5,
+     "fields": {
+        "name": "Barcoded",
+        "can_multiplex": true,
+        "is_paired_end": true
      }
   },
   {
      }
   },
   {
      "fields": {
         "scientific_name": "Strongylocentrotus purpuratus"
      }
      "fields": {
         "scientific_name": "Strongylocentrotus purpuratus"
      }
+  },
+  {
+    "pk": 1,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 1,
+      "adapter_type": 8,
+      "sequence": "ATCACG"
+    }
+  },
+  {
+    "pk": 2,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 2,
+      "adapter_type": 8,
+      "sequence": "CGATGT"
+    }
+  },
+  {
+    "pk": 3,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 3,
+      "adapter_type": 8,
+      "sequence": "TTAGGC"
+    }
+  },
+  {
+    "pk": 4,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 4,
+      "adapter_type": 8,
+      "sequence": "TGACCA"
+    }
+  },
+  {
+    "pk": 5,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 5,
+      "adapter_type": 8,
+      "sequence": "ACAGTG"
+    }
+  },
+  {
+    "pk": 6,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 6,
+      "adapter_type": 8,
+      "sequence": "GCCAAT"
+    }
+  },
+  {
+    "pk": 7,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 7,
+      "adapter_type": 8,
+      "sequence": "CAGATC"
+    }
+  },
+  {
+    "pk": 8,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 8,
+      "adapter_type": 8,
+      "sequence": "ACTTGA"
+    }
+  },
+  {
+    "pk": 9,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 9,
+      "adapter_type": 8,
+      "sequence": "GATCAG"
+    }
+  },
+  {
+    "pk": 10,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 10,
+      "adapter_type": 8,
+      "sequence": "TAGCTT"
+    }
+  },
+  {
+    "pk": 11,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 11,
+      "adapter_type": 8,
+      "sequence": "GGCTAC"
+    }
+  },
+  {
+    "pk": 12,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 12,
+      "adapter_type": 8,
+      "sequence": "CTTGTA"
+    }
+  },
+  {
+    "pk": 13,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 1,
+      "adapter_type": 7,
+      "sequence": "ATCACG"
+    }
+  },
+  {
+    "pk": 14,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 2,
+      "adapter_type": 7,
+      "sequence": "CGATGT"
+    }
+  },
+  {
+    "pk": 15,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 3,
+      "adapter_type": 7,
+      "sequence": "TTAGGC"
+    }
+  },
+  {
+    "pk": 16,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 4,
+      "adapter_type": 7,
+      "sequence": "TGACCA"
+    }
+  },
+  {
+    "pk": 17,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 5,
+      "adapter_type": 7,
+      "sequence": "ACAGTG"
+    }
+  },
+  {
+    "pk": 18,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 6,
+      "adapter_type": 7,
+      "sequence": "GCCAAT"
+    }
+  },
+  {
+    "pk": 19,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 7,
+      "adapter_type": 7,
+      "sequence": "CAGATC"
+    }
+  },
+  {
+    "pk": 20,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 8,
+      "adapter_type": 7,
+      "sequence": "ACTTGA"
+    }
+  },
+  {
+    "pk": 21,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 9,
+      "adapter_type": 7,
+      "sequence": "GATCAG"
+    }
+  },
+  {
+    "pk": 22,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 10,
+      "adapter_type": 7,
+      "sequence": "TAGCTT"
+    }
+  },
+  {
+    "pk": 23,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 11,
+      "adapter_type": 7,
+      "sequence": "GGCTAC"
+    }
+  },
+  {
+    "pk": 24,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 12,
+      "adapter_type": 7,
+      "sequence": "CTTGTA"
+    }
+  },
+  {
+    "pk": 25,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 13,
+      "adapter_type": 7,
+      "sequence": "AGTCAA"
+    }
+  },
+  {
+    "pk": 26,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 14,
+      "adapter_type": 7,
+      "sequence": "AGTTCC"
+    }
+  },
+  {
+    "pk": 27,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 15,
+      "adapter_type": 7,
+      "sequence": "ATGTCA"
+    }
+  },
+  {
+    "pk": 28,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 16,
+      "adapter_type": 7,
+      "sequence": "CCGTCC"
+    }
+  },
+  {
+    "pk": 29,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 17,
+      "adapter_type": 7,
+      "sequence": "GTAGAG"
+    }
+  },
+  {
+    "pk": 30,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 18,
+      "adapter_type": 7,
+      "sequence": "GTCCGC"
+    }
+  },
+  {
+    "pk": 31,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 19,
+      "adapter_type": 7,
+      "sequence": "GTGAAA"
+    }
+  },
+  {
+    "pk": 32,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 20,
+      "adapter_type": 7,
+      "sequence": "GTGGCC"
+    }
+  },
+  {
+    "pk": 33,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 21,
+      "adapter_type": 7,
+      "sequence": "GTTTCG"
+    }
+  },
+  {
+    "pk": 34,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 22,
+      "adapter_type": 7,
+      "sequence": "CGTACG"
+    }
+  },
+  {
+    "pk": 35,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 23,
+      "adapter_type": 7,
+      "sequence": "GAGTGG"
+    }
+  },
+  {
+    "pk": 36,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 24,
+      "adapter_type": 7,
+      "sequence": "GGTAGC"
+    }
+  },
+  {
+    "pk": 37,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 25,
+      "adapter_type": 7,
+      "sequence": "ACTGAT"
+    }
+  },
+  {
+    "pk": 38,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 26,
+      "adapter_type": 7,
+      "sequence": "ATGAGC"
+    }
+  },
+  {
+    "pk": 39,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 27,
+      "adapter_type": 7,
+      "sequence": "ATTCCT"
+    }
+  },
+  {
+    "pk": 40,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 28,
+      "adapter_type": 7,
+      "sequence": "CAAAAG"
+    }
+  },
+  {
+    "pk": 41,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 29,
+      "adapter_type": 7,
+      "sequence": "CAACTA"
+    }
+  },
+  {
+    "pk": 42,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 30,
+      "adapter_type": 7,
+      "sequence": "CACCGG"
+    }
+  },
+  {
+    "pk": 43,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 31,
+      "adapter_type": 7,
+      "sequence": "CACGAT"
+    }
+  },
+  {
+    "pk": 44,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 32,
+      "adapter_type": 7,
+      "sequence": "CACTCA"
+    }
+  },
+  {
+    "pk": 45,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 33,
+      "adapter_type": 7,
+      "sequence": "CAGGCG"
+    }
+  },
+  {
+    "pk": 46,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 34,
+      "adapter_type": 7,
+      "sequence": "CATGGC"
+    }
+  },
+  {
+    "pk": 47,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 35,
+      "adapter_type": 7,
+      "sequence": "CATTTT"
+    }
+  },
+  {
+    "pk": 48,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 36,
+      "adapter_type": 7,
+      "sequence": "CCAACA"
+    }
+  },
+  {
+    "pk": 49,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 37,
+      "adapter_type": 7,
+      "sequence": "CGGAAT"
+    }
+  },
+  {
+    "pk": 50,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 38,
+      "adapter_type": 7,
+      "sequence": "CTAGCT"
+    }
+  },
+  {
+    "pk": 51,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 39,
+      "adapter_type": 7,
+      "sequence": "CTATAC"
+    }
+  },
+  {
+    "pk": 52,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 40,
+      "adapter_type": 7,
+      "sequence": "CTCAGA"
+    }
+  },
+  {
+    "pk": 53,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 41,
+      "adapter_type": 7,
+      "sequence": "GACGAC"
+    }
+  },
+  {
+    "pk": 54,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 42,
+      "adapter_type": 7,
+      "sequence": "TAATCG"
+    }
+  },
+  {
+    "pk": 55,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 43,
+      "adapter_type": 7,
+      "sequence": "TACAGC"
+    }
+  },
+  {
+    "pk": 56,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 44,
+      "adapter_type": 7,
+      "sequence": "TATAAT"
+    }
+  },
+  {
+    "pk": 57,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 45,
+      "adapter_type": 7,
+      "sequence": "TCATTC"
+    }
+  },
+  {
+    "pk": 58,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 46,
+      "adapter_type": 7,
+      "sequence": "TCCCGA"
+    }
+  },
+  {
+    "pk": 59,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 47,
+      "adapter_type": 7,
+      "sequence": "TCGAAG"
+    }
+  },
+  {
+    "pk": 60,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 48,
+      "adapter_type": 7,
+      "sequence": "TCGGCA"
+    }
+  },
+  {
+    "pk": 61,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 1,
+      "adapter_type": 5,
+      "sequence": "ATCACG"
+    }
+  },
+  {
+    "pk": 62,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 2,
+      "adapter_type": 5,
+      "sequence": "CGATGT"
+    }
+  },
+  {
+    "pk": 63,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 3,
+      "adapter_type": 5,
+      "sequence": "TTAGGC"
+    }
+  },
+  {
+    "pk": 64,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 4,
+      "adapter_type": 5,
+      "sequence": "TGACCA"
+    }
+  },
+  {
+    "pk": 65,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 5,
+      "adapter_type": 5,
+      "sequence": "ACAGTG"
+    }
+  },
+  {
+    "pk": 66,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 6,
+      "adapter_type": 5,
+      "sequence": "GCCAAT"
+    }
+  },
+  {
+    "pk": 67,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 7,
+      "adapter_type": 5,
+      "sequence": "CAGATC"
+    }
+  },
+  {
+    "pk": 68,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 8,
+      "adapter_type": 5,
+      "sequence": "ACTTGA"
+    }
+  },
+  {
+    "pk": 69,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 9,
+      "adapter_type": 5,
+      "sequence": "GATCAG"
+    }
+  },
+  {
+    "pk": 70,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 10,
+      "adapter_type": 5,
+      "sequence": "TAGCTT"
+    }
+  },
+  {
+    "pk": 71,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 11,
+      "adapter_type": 5,
+      "sequence": "GGCTAC"
+    }
+  },
+  {
+    "pk": 72,
+    "model": "samples.MultiplexIndex",
+    "fields": {
+      "multiplex_id": 12,
+      "adapter_type": 5,
+      "sequence": "CTTGTA"
+    }
   }
 ]
   }
 ]
index 531edc6c84d0e417683e609218ac75ba2f18a1fa..d1ab6607e775562370838069031f8400a5659593 100644 (file)
@@ -12,12 +12,12 @@ logger = logging.getLogger(__name__)
 class Antibody(models.Model):
     antigene = models.CharField(max_length=500, db_index=True)
     # New field Aug/20/08
 class Antibody(models.Model):
     antigene = models.CharField(max_length=500, db_index=True)
     # New field Aug/20/08
-    # SQL to add column: 
+    # SQL to add column:
     # alter table fctracker_antibody add column "nickname" varchar(20) NULL;
     nickname = models.CharField(
         max_length=20,
         blank=True,
     # alter table fctracker_antibody add column "nickname" varchar(20) NULL;
     nickname = models.CharField(
         max_length=20,
         blank=True,
-        null=True, 
+        null=True,
         db_index=True
     )
     catalog = models.CharField(max_length=50, blank=True, null=True)
         db_index=True
     )
     catalog = models.CharField(max_length=50, blank=True, null=True)
@@ -35,9 +35,9 @@ class Cellline(models.Model):
     cellline_name = models.CharField(max_length=100, unique=True, db_index=True)
     nickname = models.CharField(max_length=20,
         blank=True,
     cellline_name = models.CharField(max_length=100, unique=True, db_index=True)
     nickname = models.CharField(max_length=20,
         blank=True,
-        null=True, 
+        null=True,
         db_index=True)
         db_index=True)
-    
+
     notes = models.TextField(blank=True)
     def __unicode__(self):
         return unicode(self.cellline_name)
     notes = models.TextField(blank=True)
     def __unicode__(self):
         return unicode(self.cellline_name)
@@ -50,7 +50,7 @@ class Condition(models.Model):
         max_length=2000, unique=True, db_index=True)
     nickname = models.CharField(max_length=20,
         blank=True,
         max_length=2000, unique=True, db_index=True)
     nickname = models.CharField(max_length=20,
         blank=True,
-        null=True, 
+        null=True,
         db_index=True,
         verbose_name = 'Short Name')
     notes = models.TextField(blank=True)
         db_index=True,
         verbose_name = 'Short Name')
     notes = models.TextField(blank=True)
@@ -61,34 +61,34 @@ class Condition(models.Model):
     class Meta:
         ordering = ["condition_name"]
 
     class Meta:
         ordering = ["condition_name"]
 
-    
+
 class ExperimentType(models.Model):
   name = models.CharField(max_length=50, unique=True)
 
   def __unicode__(self):
     return unicode(self.name)
 
 class ExperimentType(models.Model):
   name = models.CharField(max_length=50, unique=True)
 
   def __unicode__(self):
     return unicode(self.name)
 
-class Tag(models.Model): 
-  tag_name = models.CharField(max_length=100, db_index=True,blank=False,null=False) 
-  TAG_CONTEXT = ( 
-      #('Antibody','Antibody'), 
-      #('Cellline', 'Cellline'), 
-      #('Condition', 'Condition'), 
-      ('Library', 'Library'), 
-      ('ANY','ANY'), 
-  ) 
-  context = models.CharField(max_length=50, 
-      choices=TAG_CONTEXT, default='Library') 
-  def __unicode__(self): 
-    return u'%s' % (self.tag_name) 
-  class Meta: 
-    ordering = ["context","tag_name"] 
+class Tag(models.Model):
+  tag_name = models.CharField(max_length=100, db_index=True,blank=False,null=False)
+  TAG_CONTEXT = (
+      #('Antibody','Antibody'),
+      #('Cellline', 'Cellline'),
+      #('Condition', 'Condition'),
+      ('Library', 'Library'),
+      ('ANY','ANY'),
+  )
+  context = models.CharField(max_length=50,
+      choices=TAG_CONTEXT, default='Library')
+
+  def __unicode__(self):
+    return u'%s' % (self.tag_name)
+
+  class Meta:
+    ordering = ["context","tag_name"]
+
 class Species(models.Model):
 class Species(models.Model):
-  scientific_name = models.CharField(max_length=256, 
-      unique=False, 
+  scientific_name = models.CharField(max_length=256,
+      unique=False,
       db_index=True
   )
   common_name = models.CharField(max_length=256, blank=True)
       db_index=True
   )
   common_name = models.CharField(max_length=256, blank=True)
@@ -96,7 +96,7 @@ class Species(models.Model):
 
   def __unicode__(self):
     return u'%s (%s)' % (self.scientific_name, self.common_name)
 
   def __unicode__(self):
     return u'%s (%s)' % (self.scientific_name, self.common_name)
-  
+
   class Meta:
     verbose_name_plural = "species"
     ordering = ["scientific_name"]
   class Meta:
     verbose_name_plural = "species"
     ordering = ["scientific_name"]
@@ -104,18 +104,18 @@ class Species(models.Model):
   @models.permalink
   def get_absolute_url(self):
     return ('htsworkflow.frontend.samples.views.species', [str(self.id)])
   @models.permalink
   def get_absolute_url(self):
     return ('htsworkflow.frontend.samples.views.species', [str(self.id)])
-  
+
 class Affiliation(models.Model):
   name = models.CharField(max_length=256, db_index=True, verbose_name='Name')
 class Affiliation(models.Model):
   name = models.CharField(max_length=256, db_index=True, verbose_name='Name')
-  contact = models.CharField(max_length=256, null=True, blank=True,verbose_name='Lab Name')  
+  contact = models.CharField(max_length=256, null=True, blank=True,verbose_name='Lab Name')
   email = models.EmailField(null=True,blank=True)
   users = models.ManyToManyField('HTSUser', null=True, blank=True)
   users.admin_order_field = "username"
   email = models.EmailField(null=True,blank=True)
   users = models.ManyToManyField('HTSUser', null=True, blank=True)
   users.admin_order_field = "username"
-  
+
   def __unicode__(self):
     str = unicode(self.name)
     if self.contact is not None and len(self.contact) > 0:
   def __unicode__(self):
     str = unicode(self.name)
     if self.contact is not None and len(self.contact) > 0:
-      str += u' ('+self.contact+u')' 
+      str += u' ('+self.contact+u')'
     return str
 
   def Users(self):
     return str
 
   def Users(self):
@@ -127,11 +127,28 @@ class Affiliation(models.Model):
     unique_together = (("name", "contact"),)
 
 class LibraryType(models.Model):
     unique_together = (("name", "contact"),)
 
 class LibraryType(models.Model):
-  name = models.CharField(max_length=255, unique=True)
+  name = models.CharField(max_length=255, unique=True,
+                          name="Adapter Type")
+  is_paired_end = models.BooleanField(default=True,
+                    help_text="can you do a paired end run with this adapter")
+  can_multiplex = models.BooleanField(default=True,
+                    help_text="Does this adapter provide multiplexing?")
 
   def __unicode__(self):
 
   def __unicode__(self):
-    return unicode(self.name)
+      return unicode(self.name)
+
+  class Meta:
+      ordering = ["-id"]
+
 
 
+class MultiplexIndex(models.Model):
+    """Map adapter types to the multiplex sequence"""
+    adapter_type = models.ForeignKey(LibraryType)
+    multiplex_id = models.CharField(max_length=3, null=False)
+    sequence = models.CharField(max_length=12, blank=True, null=True)
+
+    class Meta:
+        unique_together = ('adapter_type', 'multiplex_id')
 
 class Library(models.Model):
   id = models.CharField(max_length=10, primary_key=True)
 
 class Library(models.Model):
   id = models.CharField(max_length=10, primary_key=True)
@@ -149,14 +166,18 @@ class Library(models.Model):
                                 blank=True,null=True)
   REPLICATE_NUM = ((1,1),(2,2),(3,3),(4,4))
   replicate =  models.PositiveSmallIntegerField(choices=REPLICATE_NUM,
                                 blank=True,null=True)
   REPLICATE_NUM = ((1,1),(2,2),(3,3),(4,4))
   replicate =  models.PositiveSmallIntegerField(choices=REPLICATE_NUM,
-                                                blank=True,null=True) 
+                                                blank=True,null=True)
   experiment_type = models.ForeignKey(ExperimentType)
   experiment_type = models.ForeignKey(ExperimentType)
-  library_type = models.ForeignKey(LibraryType, blank=True, null=True)
+  library_type = models.ForeignKey(LibraryType, blank=True, null=True,
+                                   verbose_name="Adapter Type")
+  multiplex_id = models.CharField(max_length=128,
+                                  blank=True, null=True,
+                                  verbose_name="Index ID")
   creation_date = models.DateField(blank=True, null=True)
   creation_date = models.DateField(blank=True, null=True)
-  made_for = models.CharField(max_length=50, blank=True, 
+  made_for = models.CharField(max_length=50, blank=True,
                               verbose_name='ChIP/DNA/RNA Made By')
   made_by = models.CharField(max_length=50, blank=True, default="Lorian")
                               verbose_name='ChIP/DNA/RNA Made By')
   made_by = models.CharField(max_length=50, blank=True, default="Lorian")
-  
+
   PROTOCOL_END_POINTS = (
       ('?', 'Unknown'),
       ('Sample', 'Raw sample'),
   PROTOCOL_END_POINTS = (
       ('?', 'Unknown'),
       ('Sample', 'Raw sample'),
@@ -173,14 +194,14 @@ class Library(models.Model):
   stopping_point = models.CharField(max_length=25,
                                     choices=PROTOCOL_END_POINTS,
                                     default='Done')
   stopping_point = models.CharField(max_length=25,
                                     choices=PROTOCOL_END_POINTS,
                                     default='Done')
-  
+
   amplified_from_sample = models.ForeignKey('self',
                             related_name='amplified_into_sample',
                             blank=True, null=True)
   amplified_from_sample = models.ForeignKey('self',
                             related_name='amplified_into_sample',
                             blank=True, null=True)
-  
-  undiluted_concentration = models.DecimalField("Concentration", 
+
+  undiluted_concentration = models.DecimalField("Concentration",
       max_digits=5, decimal_places=2, blank=True, null=True,
       max_digits=5, decimal_places=2, blank=True, null=True,
-      help_text=u"Undiluted concentration (ng/\u00b5l)") 
+      help_text=u"Undiluted concentration (ng/\u00b5l)")
       # note \u00b5 is the micro symbol in unicode
   successful_pM = models.DecimalField(max_digits=9,
                                       decimal_places=1, blank=True, null=True)
       # note \u00b5 is the micro symbol in unicode
   successful_pM = models.DecimalField(max_digits=9,
                                       decimal_places=1, blank=True, null=True)
@@ -190,27 +211,59 @@ class Library(models.Model):
   notes = models.TextField(blank=True)
 
   bioanalyzer_summary = models.TextField(blank=True,default="")
   notes = models.TextField(blank=True)
 
   bioanalyzer_summary = models.TextField(blank=True,default="")
-  bioanalyzer_concentration = models.DecimalField(max_digits=5, 
+  bioanalyzer_concentration = models.DecimalField(max_digits=5,
                                 decimal_places=2, blank=True, null=True,
                                 help_text=u"(ng/\u00b5l)")
   bioanalyzer_image_url = models.URLField(blank=True,default="")
                                 decimal_places=2, blank=True, null=True,
                                 help_text=u"(ng/\u00b5l)")
   bioanalyzer_image_url = models.URLField(blank=True,default="")
-  
+
   def __unicode__(self):
     return u'#%s: %s' % (self.id, self.library_name)
   def __unicode__(self):
     return u'#%s: %s' % (self.id, self.library_name)
-  
+
   class Meta:
       verbose_name_plural = "libraries"
   class Meta:
       verbose_name_plural = "libraries"
-      #ordering = ["-creation_date"] 
+      #ordering = ["-creation_date"]
       ordering = ["-id"]
       ordering = ["-id"]
-  
+
   def antibody_name(self):
   def antibody_name(self):
-    str ='<a target=_self href="/admin/samples/antibody/'+self.antibody.id.__str__()+'/" title="'+self.antibody.__str__()+'">'+self.antibody.label+'</a>' 
+    str ='<a target=_self href="/admin/samples/antibody/'+self.antibody.id.__str__()+'/" title="'+self.antibody.__str__()+'">'+self.antibody.label+'</a>'
     return str
   antibody_name.allow_tags = True
 
   def organism(self):
     return self.library_species.common_name
 
     return str
   antibody_name.allow_tags = True
 
   def organism(self):
     return self.library_species.common_name
 
+  def index_sequences(self):
+      """Return a dictionary of multiplex index id to sequence
+      Return None if the library can't multiplex,
+
+      """
+      if self.library_type is None:
+          return None
+      if not self.library_type.can_multiplex:
+          return None
+      if self.multiplex_id is None or len(self.multiplex_id) == 0:
+          return 'Err: id empty'
+      sequences = {}
+      multiplex_ids = self.multiplex_id.split(',')
+      for multiplex_id in multiplex_ids:
+          try:
+              multiplex = MultiplexIndex.objects.get(
+                  adapter_type = self.library_type.id,
+                  multiplex_id = multiplex_id)
+              sequences[multiplex_id] = multiplex.sequence
+          except MultiplexIndex.DoesNotExist, e:
+              sequences[multiplex_id] = 'Err: index not found'
+      return sequences
+
+  def index_sequence_text(self, seperator=' '):
+      """Return formatted multiplex index sequences"""
+      sequences = self.index_sequences()
+      multiplex_ids = sequences.keys()
+      multiplex_ids.sort()
+      return seperator.join(( "%s:%s" %(i,sequences[i]) for i in multiplex_ids))
+  index_sequence_text.short_description = "Index"
+
+
   def affiliation(self):
     affs = self.affiliations.all().order_by('name')
     tstr = ''
   def affiliation(self):
     affs = self.affiliations.all().order_by('name')
     tstr = ''
@@ -218,7 +271,7 @@ class Library(models.Model):
     for t in affs:
         ar.append(t.__unicode__())
     return '%s' % (", ".join(ar))
     for t in affs:
         ar.append(t.__unicode__())
     return '%s' % (", ".join(ar))
-    
+
   def is_archived(self):
     """
     returns True if archived else False
   def is_archived(self):
     """
     returns True if archived else False
@@ -235,7 +288,7 @@ class Library(models.Model):
           name = "Lookup Error"
           logger.error("protocol stopping point in database didn't match names in library model")
       return name
           name = "Lookup Error"
           logger.error("protocol stopping point in database didn't match names in library model")
       return name
-      
+
 
   def libtags(self):
     affs = self.tags.all().order_by('tag_name')
 
   def libtags(self):
     affs = self.tags.all().order_by('tag_name')
@@ -245,7 +298,7 @@ class Library(models.Model):
     return u'%s' % ( ", ".join(ar))
 
   def DataRun(self):
     return u'%s' % ( ", ".join(ar))
 
   def DataRun(self):
-    str ='<a target=_self href="/admin/experiments/datarun/?q='+self.id+'" title="Check All Data Runs for This Specific Library ..." ">Data Run</a>' 
+    str ='<a target=_self href="/admin/experiments/datarun/?q='+self.id+'" title="Check All Data Runs for This Specific Library ..." ">Data Run</a>'
     return str
   DataRun.allow_tags = True
 
     return str
   DataRun.allow_tags = True
 
@@ -273,21 +326,21 @@ class Library(models.Model):
         if res[1] > rc_thr[1]:
           bgcolor ='#00ccff'  # Blue
         else:
         if res[1] > rc_thr[1]:
           bgcolor ='#00ccff'  # Blue
         else:
-           if res[1] > rc_thr[2]: 
+           if res[1] > rc_thr[2]:
              bgcolor ='#ffcc33'  # Orange
       tstr = '<div style="background-color:'+bgcolor+';color:black">'
       tstr += res[0].__unicode__()+' Lanes, '+rc+' M Reads'
       tstr += '</div>'
              bgcolor ='#ffcc33'  # Orange
       tstr = '<div style="background-color:'+bgcolor+';color:black">'
       tstr += res[0].__unicode__()+' Lanes, '+rc+' M Reads'
       tstr += '</div>'
-    else: tstr = 'not processed yet' 
+    else: tstr = 'not processed yet'
     return tstr
   aligned_reads.allow_tags = True
     return tstr
   aligned_reads.allow_tags = True
-  
+
   def public(self):
     SITE_ROOT = '/'
     summary_url = self.get_absolute_url()
     return '<a href="%s">S</a>' % (summary_url,)
   public.allow_tags = True
   def public(self):
     SITE_ROOT = '/'
     summary_url = self.get_absolute_url()
     return '<a href="%s">S</a>' % (summary_url,)
   public.allow_tags = True
-    
+
   @models.permalink
   def get_absolute_url(self):
     return ('htsworkflow.frontend.samples.views.library_to_flowcells', [str(self.id)])
   @models.permalink
   def get_absolute_url(self):
     return ('htsworkflow.frontend.samples.views.library_to_flowcells', [str(self.id)])
@@ -311,7 +364,7 @@ class HTSUser(User):
     def __unicode__(self):
         #return unicode(self.username) + u" (" + unicode(self.get_full_name()) + u")"
         return unicode(self.get_full_name()) + u' (' + unicode(self.username) + ')'
     def __unicode__(self):
         #return unicode(self.username) + u" (" + unicode(self.get_full_name()) + u")"
         return unicode(self.get_full_name()) + u' (' + unicode(self.username) + ')'
-    
+
 def HTSUserInsertID(sender, instance, **kwargs):
     """
     Force addition of HTSUsers when someone just modifies the auth_user object
 def HTSUserInsertID(sender, instance, **kwargs):
     """
     Force addition of HTSUsers when someone just modifies the auth_user object
@@ -321,5 +374,5 @@ def HTSUserInsertID(sender, instance, **kwargs):
         cursor = connection.cursor()
         cursor.execute('INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' % (instance.id,))
         cursor.close()
         cursor = connection.cursor()
         cursor.execute('INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' % (instance.id,))
         cursor.close()
-    
+
 post_save.connect(HTSUserInsertID, sender=User)
 post_save.connect(HTSUserInsertID, sender=User)
index c125a1ca4b44c11dd38650a38371a6f1ed04d0dc..1800fe54f2ab8219aa138cb06399e351ea22fcf2 100644 (file)
@@ -1,11 +1,11 @@
 <div id="librarydetail"  about="{{lib.get_absolute_url}}">
   <div class="library_identity">
 <div id="librarydetail"  about="{{lib.get_absolute_url}}">
   <div class="library_identity">
-    <h2>Library Name</h2>  
-    <b>Library ID</b>: 
+    <h2>Library Name</h2>
+    <b>Library ID</b>:
        <a href="{{lib.get_absolute_url}}"><span property="libns:library_id">{{ lib.id }}</span></a>
        {% if user.is_staff %}<a href="{{lib.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif"/></a>{% endif %}
        <br/>
        <a href="{{lib.get_absolute_url}}"><span property="libns:library_id">{{ lib.id }}</span></a>
        {% if user.is_staff %}<a href="{{lib.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif"/></a>{% endif %}
        <br/>
-    <b>Name</b>: 
+    <b>Name</b>:
       <span property="libns:name">{{ lib.library_name }}</span>
     <br/>
     <b>Affiliations</b>:
       <span property="libns:name">{{ lib.library_name }}</span>
     <br/>
     <b>Affiliations</b>:
@@ -19,7 +19,7 @@
   </div>
   <div class="library_sample_detail">
     <h2>Sample Details</h2>
   </div>
   <div class="library_sample_detail">
     <h2>Sample Details</h2>
-    <b>Species</b>: 
+    <b>Species</b>:
       <span property="libns:species" content="{{lib.library_species.scientific_name}}"><a href="{{lib.library_species.get_absolute_url}}">{{ lib.library_species.scientific_name }}</a></span>
     <br/>
     <b>Experiment Type</b>:
       <span property="libns:species" content="{{lib.library_species.scientific_name}}"><a href="{{lib.library_species.get_absolute_url}}">{{ lib.library_species.scientific_name }}</a></span>
     <br/>
     <b>Experiment Type</b>:
@@ -47,7 +47,7 @@
     <br/>
     {% endif %}
     {% if lib.replicate %}
     <br/>
     {% endif %}
     {% if lib.replicate %}
-    <b>Replicate</b>: 
+    <b>Replicate</b>:
        <span property="libns:replicate">{{ lib.replicate }}</span>
     <br/>
     {% endif %}
        <span property="libns:replicate">{{ lib.replicate }}</span>
     <br/>
     {% endif %}
     <b>Library Type</b>:
        <span property="libns:library_type">{{ lib.library_type }}</span>
     <br/>
     <b>Library Type</b>:
        <span property="libns:library_type">{{ lib.library_type }}</span>
     <br/>
+    <b>Multiplex Index</b>:
+       <span property="libns:multiplex_index">{{ lib.index_sequence_text }}</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>
     <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>: 
+    <br/>
+    <b>Made By</b>:
       <span property="libns:made_by">{{ lib.made_by }}</span>
     <br/>
     {% if lib.gel_cut_size %}
       <span property="libns:made_by">{{ lib.made_by }}</span>
     <br/>
     {% if lib.gel_cut_size %}
-    <b>Gel Cut Size</b>: 
+    <b>Gel Cut Size</b>:
       <span property="libns:gel_cut" datatype="xsd:decimal">{{ lib.gel_cut_size }}</span>
     <br/>
     {% endif %}
     {% if lib.insert_size %}
       <span property="libns:gel_cut" datatype="xsd:decimal">{{ lib.gel_cut_size }}</span>
     <br/>
     {% endif %}
     {% if lib.insert_size %}
-    <b>Insert Size</b>: 
+    <b>Insert Size</b>:
       <span property="libns:insert_size" datatype="xsd:decimal">{{ lib.insert_size }}</span>
     <br/>
     {% endif %}
     {% if lib.undiluted_concentration %}
       <span property="libns:insert_size" datatype="xsd:decimal">{{ lib.insert_size }}</span>
     <br/>
     {% endif %}
     {% if lib.undiluted_concentration %}
-    <b>Concentration</b>: 
+    <b>Concentration</b>:
       <span property="libns:concentration">{{ lib.undiluted_concentration }} ng/µl</span>
     <br/>
     {% endif %}
       <span property="libns:concentration">{{ lib.undiluted_concentration }} ng/µl</span>
     <br/>
     {% endif %}
index dd893e186ebf8cf990f777357f471a48412b51d2..888da7d03e59ab7175d1a7309257dd443bfae736 100644 (file)
@@ -1,10 +1,12 @@
 #!/usr/bin/env python
 
 #!/usr/bin/env python
 
+import csv
 from ConfigParser import RawConfigParser
 import logging
 from optparse import OptionParser, IndentedHelpFormatter
 import os
 import sys
 from ConfigParser import RawConfigParser
 import logging
 from optparse import OptionParser, IndentedHelpFormatter
 import os
 import sys
+import types
 import urllib
 import urllib2
 
 import urllib
 import urllib2
 
@@ -16,8 +18,12 @@ except ImportError, e:
 from htsworkflow.frontend.auth import apidata
 from htsworkflow.util import api
 from htsworkflow.util.url import normalize_url
 from htsworkflow.frontend.auth import apidata
 from htsworkflow.util import api
 from htsworkflow.util.url import normalize_url
-from htsworkflow.pipelines.genome_mapper import getAvailableGenomes
-from htsworkflow.pipelines.genome_mapper import constructMapperDict
+from htsworkflow.pipelines.genome_mapper import \
+     getAvailableGenomes, \
+     constructMapperDict
+from htsworkflow.pipelines.runfolder import LANE_LIST
+# JSON dictionaries use strings
+LANE_LIST_JSON = [ str(l) for l in LANE_LIST ]
 
 __docformat__ = "restructredtext en"
 
 
 __docformat__ = "restructredtext en"
 
@@ -28,17 +34,15 @@ GERALD_CONFIG_SECTION = 'gerald_config'
 #Disable or enable commandline arg parsing; disabled by default.
 DISABLE_CMDLINE = True
 
 #Disable or enable commandline arg parsing; disabled by default.
 DISABLE_CMDLINE = True
 
-LANE_LIST = ['1','2','3','4','5','6','7','8']
-
 class FlowCellNotFound(Exception): pass
 class WebError404(Exception): pass
 
 def retrieve_flowcell_info(base_host_url, flowcell):
     """
 class FlowCellNotFound(Exception): pass
 class WebError404(Exception): pass
 
 def retrieve_flowcell_info(base_host_url, flowcell):
     """
-    Return a dictionary describing a 
+    Return a dictionary describing a
     """
     url = api.flowcell_url(base_host_url, flowcell)
     """
     url = api.flowcell_url(base_host_url, flowcell)
-  
+
     try:
         apipayload = urllib.urlencode(apidata)
         web = urllib2.urlopen(url, apipayload)
     try:
         apipayload = urllib.urlencode(apidata)
         web = urllib2.urlopen(url, apipayload)
@@ -47,19 +51,19 @@ def retrieve_flowcell_info(base_host_url, flowcell):
         logging.error(errmsg)
         logging.error('opened %s' % (url,))
         raise IOError(errmsg)
         logging.error(errmsg)
         logging.error('opened %s' % (url,))
         raise IOError(errmsg)
-    
+
     contents = web.read()
     headers = web.info()
 
     if web.code == 403:
         msg = "403 - Forbbidden, probably need api key"
         raise FlowCellNotFound(msg)
     contents = web.read()
     headers = web.info()
 
     if web.code == 403:
         msg = "403 - Forbbidden, probably need api key"
         raise FlowCellNotFound(msg)
-    
+
     if web.code == 404:
         msg = "404 - Not Found: Flowcell (%s); base_host_url (%s);\n full url(%s)\n " \
               "Did you get right port #?" % (flowcell, base_host_url, url)
         raise FlowCellNotFound(msg)
     if web.code == 404:
         msg = "404 - Not Found: Flowcell (%s); base_host_url (%s);\n full url(%s)\n " \
               "Did you get right port #?" % (flowcell, base_host_url, url)
         raise FlowCellNotFound(msg)
-  
+
     if len(contents) == 0:
         msg = "No information for flowcell (%s) returned; full url(%s)" % (flowcell, url)
         raise FlowCellNotFound(msg)
     if len(contents) == 0:
         msg = "No information for flowcell (%s) returned; full url(%s)" % (flowcell, url)
         raise FlowCellNotFound(msg)
@@ -75,7 +79,7 @@ def is_sequencing(lane_info):
         return True
     else:
         return False
         return True
     else:
         return False
-    
+
 def group_lane_parameters(flowcell_info):
     """
     goup lanes that can share GERALD configuration blocks.
 def group_lane_parameters(flowcell_info):
     """
     goup lanes that can share GERALD configuration blocks.
@@ -83,11 +87,12 @@ def group_lane_parameters(flowcell_info):
     (The same species, read length, and eland vs sequencing)
     """
     lane_groups = {}
     (The same species, read length, and eland vs sequencing)
     """
     lane_groups = {}
-    for lane_number, lane_info in flowcell_info['lane_set'].items():
-        index = (lane_info['read_length'],
-                 lane_info['library_species'],
-                 is_sequencing(lane_info))
-        lane_groups.setdefault(index, []).append(lane_number)
+    for lane_number, lane_contents in flowcell_info['lane_set'].items():
+        for lane_info in lane_contents:
+            index = (lane_info['read_length'],
+                     lane_info['library_species'],
+                     is_sequencing(lane_info))
+            lane_groups.setdefault(index, []).append(lane_number)
     return lane_groups
 
 def format_gerald_header(flowcell_info):
     return lane_groups
 
 def format_gerald_header(flowcell_info):
@@ -103,10 +108,12 @@ def format_gerald_header(flowcell_info):
     config += ['Flowcell Notes:']
     config.extend(flowcell_info['notes'].split('\r\n'))
     config += ['']
     config += ['Flowcell Notes:']
     config.extend(flowcell_info['notes'].split('\r\n'))
     config += ['']
-    for lane_number in LANE_LIST:
-        lane_info = flowcell_info['lane_set'][lane_number]
-        config += ['Lane%s: %s | %s' % (lane_number, lane_info['library_id'],
-                                        lane_info['library_name'])]
+    for lane_number in LANE_LIST_JSON:
+        lane_contents = flowcell_info['lane_set'][lane_number]
+        for lane_info in lane_contents:
+            config += ['Lane%s: %s | %s' % (lane_number,
+                                            lane_info['library_id'],
+                                            lane_info['library_name'])]
 
     config += ['']
     return "\n# ".join(config)
 
     config += ['']
     return "\n# ".join(config)
@@ -134,14 +141,14 @@ def format_gerald_config(options, flowcell_info, genome_map):
         read_length, species, is_sequencing = lane_index
         lane_numbers.sort()
         lane_prefix = u"".join(lane_numbers)
         read_length, species, is_sequencing = lane_index
         lane_numbers.sort()
         lane_prefix = u"".join(lane_numbers)
-        
+
         species_path = genome_map.get(species, None)
         logging.debug("Looked for genome '%s' got location '%s'" % (species, species_path))
         if not is_sequencing and species_path is None:
             no_genome_msg = "Forcing lanes %s to sequencing as there is no genome for %s"
             logging.warning(no_genome_msg % (lane_numbers, species))
             is_sequencing = True
         species_path = genome_map.get(species, None)
         logging.debug("Looked for genome '%s' got location '%s'" % (species, species_path))
         if not is_sequencing and species_path is None:
             no_genome_msg = "Forcing lanes %s to sequencing as there is no genome for %s"
             logging.warning(no_genome_msg % (lane_numbers, species))
             is_sequencing = True
-            
+
         if is_sequencing:
             config += ['%s:ANALYSIS sequence%s' % (lane_prefix, sequence_suffix)]
         else:
         if is_sequencing:
             config += ['%s:ANALYSIS sequence%s' % (lane_prefix, sequence_suffix)]
         else:
@@ -150,16 +157,16 @@ def format_gerald_config(options, flowcell_info, genome_map):
         #config += ['%s:READ_LENGTH %s' % ( lane_prefix, read_length ) ]
         config += ['%s:USE_BASES Y%s' % ( lane_prefix, read_length ) ]
 
         #config += ['%s:READ_LENGTH %s' % ( lane_prefix, read_length ) ]
         config += ['%s:USE_BASES Y%s' % ( lane_prefix, read_length ) ]
 
-    # add in option for running script after 
+    # add in option for running script after
     if not (options.post_run is None or options.runfolder is None):
         runfolder = os.path.abspath(options.runfolder)
         post_run = options.post_run  % {'runfolder': runfolder}
         config += ['POST_RUN_COMMAND %s' % (post_run,) ]
     if not (options.post_run is None or options.runfolder is None):
         runfolder = os.path.abspath(options.runfolder)
         post_run = options.post_run  % {'runfolder': runfolder}
         config += ['POST_RUN_COMMAND %s' % (post_run,) ]
-        
+
     config += [''] # force trailing newline
     config += [''] # force trailing newline
-    
+
     return "\n".join(config)
     return "\n".join(config)
-              
+
 class DummyOptions:
   """
   Used when command line parsing is disabled; default
 class DummyOptions:
   """
   Used when command line parsing is disabled; default
@@ -171,14 +178,14 @@ class DummyOptions:
     self.genome_dir = None
 
 class PreformattedDescriptionFormatter(IndentedHelpFormatter):
     self.genome_dir = None
 
 class PreformattedDescriptionFormatter(IndentedHelpFormatter):
-  
+
   #def format_description(self, description):
   #def format_description(self, description):
-  #  
+  #
   #  if description:
   #      return description + "\n"
   #  else:
   #     return ""
   #  if description:
   #      return description + "\n"
   #  else:
   #     return ""
-      
+
   def format_epilog(self, epilog):
     """
     It was removing my preformated epilog, so this should override
   def format_epilog(self, epilog):
     """
     It was removing my preformated epilog, so this should override
@@ -197,33 +204,33 @@ def constructOptionParser():
     parser = OptionParser(formatter=PreformattedDescriptionFormatter())
 
     parser.set_description('Retrieves eland config file from hts_frontend web frontend.')
     parser = OptionParser(formatter=PreformattedDescriptionFormatter())
 
     parser.set_description('Retrieves eland config file from hts_frontend web frontend.')
-  
+
     parser.epilog = """
 Config File:
   * %s (System wide)
   * %s (User specific; overrides system)
   * command line overrides all config file options
     parser.epilog = """
 Config File:
   * %s (System wide)
   * %s (User specific; overrides system)
   * command line overrides all config file options
-  
+
   Example Config File:
   Example Config File:
-  
+
     [%s]
     config_host: http://somewhere.domain:port
     genome_dir: /path to search for genomes
     post_run: runfolder -o <destdir> %%(runfolder)s
     [%s]
     config_host: http://somewhere.domain:port
     genome_dir: /path to search for genomes
     post_run: runfolder -o <destdir> %%(runfolder)s
-    
+
 """ % (CONFIG_SYSTEM, CONFIG_USER, GERALD_CONFIG_SECTION)
 """ % (CONFIG_SYSTEM, CONFIG_USER, GERALD_CONFIG_SECTION)
-  
+
     #Special formatter for allowing preformatted description.
     ##parser.format_epilog(PreformattedDescriptionFormatter())
 
     parser.add_option("-u", "--url",
                       action="store", type="string", dest="url")
     #Special formatter for allowing preformatted description.
     ##parser.format_epilog(PreformattedDescriptionFormatter())
 
     parser.add_option("-u", "--url",
                       action="store", type="string", dest="url")
-  
+
     parser.add_option("-o", "--output-file",
                       action="store", type="string", dest="output_filepath",
                       help="config file destination. If runfolder is specified defaults "
                            "to <runfolder>/config-auto.txt" )
     parser.add_option("-o", "--output-file",
                       action="store", type="string", dest="output_filepath",
                       help="config file destination. If runfolder is specified defaults "
                            "to <runfolder>/config-auto.txt" )
-  
+
     parser.add_option("-f", "--flowcell",
                       action="store", type="string", dest="flowcell")
 
     parser.add_option("-f", "--flowcell",
                       action="store", type="string", dest="flowcell")
 
@@ -234,10 +241,17 @@ Config File:
                       action="store", type="string",
                       help="specify runfolder for post_run command ")
 
                       action="store", type="string",
                       help="specify runfolder for post_run command ")
 
+    parser.add_option("--sample-sheet", default=None,
+                      help="path to save demultiplexing sample sheet")
+
+    parser.add_option("--operator", default='', help="Name of sequencer operator")
+    parser.add_option("--recipe", default="Unknown",
+                      help="specify recipe name")
+
     parser.add_option('-v', '--verbose', action='store_true', default=False,
                        help='increase logging verbosity')
     return parser
     parser.add_option('-v', '--verbose', action='store_true', default=False,
                        help='increase logging verbosity')
     return parser
-    
+
 def constructConfigParser():
     """
     returns a pre-setup config parser
 def constructConfigParser():
     """
     returns a pre-setup config parser
@@ -246,7 +260,7 @@ def constructConfigParser():
     parser.read([CONFIG_SYSTEM, CONFIG_USER])
     if not parser.has_section(GERALD_CONFIG_SECTION):
         parser.add_section(GERALD_CONFIG_SECTION)
     parser.read([CONFIG_SYSTEM, CONFIG_USER])
     if not parser.has_section(GERALD_CONFIG_SECTION):
         parser.add_section(GERALD_CONFIG_SECTION)
-  
+
     return parser
 
 
     return parser
 
 
@@ -264,13 +278,13 @@ def getCombinedOptions(argv=None):
         options = DummyOptions()
     else:
         options, args = cl_parser.parse_args(argv)
         options = DummyOptions()
     else:
         options, args = cl_parser.parse_args(argv)
-        
+
     if options.url is None:
         if conf_parser.has_option(GERALD_CONFIG_SECTION, 'config_host'):
             options.url = conf_parser.get(GERALD_CONFIG_SECTION, 'config_host')
     if options.url is None:
         if conf_parser.has_option(GERALD_CONFIG_SECTION, 'config_host'):
             options.url = conf_parser.get(GERALD_CONFIG_SECTION, 'config_host')
-      
+
     options.url = normalize_url(options.url)
     options.url = normalize_url(options.url)
-  
+
     if options.genome_dir is None:
         if conf_parser.has_option(GERALD_CONFIG_SECTION, 'genome_dir'):
             options.genome_dir = conf_parser.get(GERALD_CONFIG_SECTION, 'genome_dir')
     if options.genome_dir is None:
         if conf_parser.has_option(GERALD_CONFIG_SECTION, 'genome_dir'):
             options.genome_dir = conf_parser.get(GERALD_CONFIG_SECTION, 'genome_dir')
@@ -283,7 +297,7 @@ def getCombinedOptions(argv=None):
     if options.output_filepath is None:
         if options.runfolder is not None:
             options.output_filepath = os.path.join(options.runfolder, 'config-auto.txt')
     if options.output_filepath is None:
         if options.runfolder is not None:
             options.output_filepath = os.path.join(options.runfolder, 'config-auto.txt')
-            
+
     return options
 
 
     return options
 
 
@@ -298,7 +312,7 @@ def saveConfigFile(options):
   logging.info(u'      FC: %s' % (options.flowcell,))
   #logging.info(': %s' % (options.genome_dir,))
   logging.info(u'post_run: %s' % ( unicode(options.post_run),))
   logging.info(u'      FC: %s' % (options.flowcell,))
   #logging.info(': %s' % (options.genome_dir,))
   logging.info(u'post_run: %s' % ( unicode(options.post_run),))
-   
+
   flowcell_info = retrieve_flowcell_info(options.url, options.flowcell)
 
   logging.debug('genome_dir: %s' % ( options.genome_dir, ))
   flowcell_info = retrieve_flowcell_info(options.url, options.flowcell)
 
   logging.debug('genome_dir: %s' % ( options.genome_dir, ))
@@ -306,16 +320,107 @@ def saveConfigFile(options):
   genome_map = constructMapperDict(available_genomes)
   logging.debug('available genomes: %s' % ( unicode( genome_map.keys() ),))
 
   genome_map = constructMapperDict(available_genomes)
   logging.debug('available genomes: %s' % ( unicode( genome_map.keys() ),))
 
-  config = format_gerald_config(options, flowcell_info, genome_map)
-
-  if options.output_filepath is not None:
-      outstream = open(options.output_filepath, 'w')
-      logging.info('Writing config file to %s' % (options.output_filepath,))
+  #config = format_gerald_config(options, flowcell_info, genome_map)
+  #
+  #if options.output_filepath is not None:
+  #    outstream = open(options.output_filepath, 'w')
+  #    logging.info('Writing config file to %s' % (options.output_filepath,))
+  #else:
+  #    outstream = sys.stdout
+  #
+  #outstream.write(config)
+
+  if options.sample_sheet is None:
+      pass
+  elif options.sample_sheet == '-':
+      save_sample_sheet(sys.stdout, options, flowcell_info)
   else:
   else:
-      outstream = sys.stdout
-      
-  outstream.write(config)
-  
+      stream = open(options.sample_sheet,'w')
+      save_sample_sheet(stream, options, flowcell_info)
+
+
+def save_sample_sheet(outstream, options, flowcell_info):
+    sample_sheet_fields = ['FCID', 'Lane', 'SampleID', 'SampleRef', 'Index',
+                           'Description', 'Control', 'Recipe', 'Operator',
+                           'SampleProject']
+    illumina_to_htsw_map = {'FCID': 'flowcell',
+                            'Lane': 'lane_number',
+                            'SampleID': 'library_id',
+                            'SampleRef': format_sampleref,
+                            'Description': 'library_name',
+                            'Control': format_control_lane,
+                            'Recipe': format_recipe_name,
+                            'Operator': format_operator_name}
+    out = csv.DictWriter(outstream, sample_sheet_fields)
+    out.writeheader()
+    for lane_number in LANE_LIST:
+        lane_contents = flowcell_info['lane_set'][str(lane_number)]
+
+        pooled_lane_contents = []
+        for library in lane_contents:
+            # build common attributes
+            renamed = {}
+            for illumina_name in sample_sheet_fields:
+                htsw_field = illumina_to_htsw_map.get(illumina_name, None)
+                if htsw_field is None:
+                    continue
+                if callable(htsw_field):
+                    renamed[illumina_name] = htsw_field(options,
+                                                        flowcell_info,
+                                                        library)
+                else:
+                    renamed[illumina_name] = library[htsw_field]
+
+            pooled_lane_contents.extend(format_pooled_libraries(renamed, library))
+
+        if len(pooled_lane_contents) > 1:
+            for row in pooled_lane_contents:
+                out.writerow(row)
+
+
+def format_sampleref(options, flowcell_info, sample):
+    return sample['library_species'].replace(' ', '_')
+
+
+def format_control_lane(options, flowcell_info, sample):
+    if sample['lane_number'] == flowcell_info['control_lane']:
+        return 'Y'
+    else:
+        return 'N'
+
+
+def format_recipe_name(options, flowcell_info, sample):
+    return options.recipe
+
+
+def format_operator_name(options, flowcell_info, sample):
+    return options.operator
+
+
+def format_pooled_libraries(shared, library):
+    sequences = library.get('index_sequence', None)
+    if sequences is None:
+        return []
+    elif type(sequences) in types.StringTypes:
+        shared['Index'] = sequences
+        shared['SampleProject'] = library['library_id']
+        return [shared]
+    else:
+        pooled = []
+        multiplex_ids = sequences.keys()
+        multiplex_ids.sort(key=int)
+        for multiplex_id in multiplex_ids:
+            sample = {}
+            sample.update(shared)
+            sample['Index'] = sequences[multiplex_id]
+            sample['SampleProject'] = format_project_name(library,
+                                                          multiplex_id)
+            pooled.append(sample)
+        return pooled
+
+
+def format_project_name(library, multiplex_id):
+    library_id = library['library_id']
+    return "%s_index%s" % (library_id, multiplex_id)
 
 
 
 
-  
index f655779bbb6edaf2c00a56e66284331f19215b4b..cb5650191e79f886da243c9837bcfa61291fbb1f 100644 (file)
@@ -1,15 +1,20 @@
+import csv
 import os
 import re
 import os
 import re
+from StringIO import StringIO
 
 try:
     import json
 except ImportError, e:
     import simplejson as json
 
 try:
     import json
 except ImportError, e:
     import simplejson as json
-    
+
 from django.test import TestCase
 
 from htsworkflow.frontend.auth import apidata
 from django.test import TestCase
 
 from htsworkflow.frontend.auth import apidata
-from htsworkflow.pipelines.retrieve_config import format_gerald_config, getCombinedOptions
+from htsworkflow.pipelines.retrieve_config import \
+     format_gerald_config, \
+     getCombinedOptions, \
+     save_sample_sheet
 
 class RetrieveTestCases(TestCase):
     fixtures = ['test_flowcells.json']
 
 class RetrieveTestCases(TestCase):
     fixtures = ['test_flowcells.json']
@@ -20,13 +25,11 @@ class RetrieveTestCases(TestCase):
     def test_format_gerald(self):
         flowcell_request = self.client.get('/experiments/config/FC12150/json', apidata)
         self.failUnlessEqual(flowcell_request.status_code, 200)
     def test_format_gerald(self):
         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)
 
         flowcell_info = json.loads(flowcell_request.content)
 
-        options = getCombinedOptions(['-f','FC12150','-g',os.getcwd()])        
+        options = getCombinedOptions(['-f','FC12150','-g',os.getcwd()])
         genome_map = {u'Homo sapiens': '/tmp/hg18' }
         genome_map = {u'Homo sapiens': '/tmp/hg18' }
-        
+
         config = format_gerald_config(options, flowcell_info, genome_map)
         config_lines = config.split('\n')
         lane3 = [ line for line in config_lines if re.search('Lane3', line) ]
         config = format_gerald_config(options, flowcell_info, genome_map)
         config_lines = config.split('\n')
         lane3 = [ line for line in config_lines if re.search('Lane3', line) ]
@@ -39,7 +42,31 @@ class RetrieveTestCases(TestCase):
         sequencing = [ line for line in config_lines if re.search('sequence_pair', line) ]
         self.failUnlessEqual(len(sequencing), 2)
 
         sequencing = [ line for line in config_lines if re.search('sequence_pair', line) ]
         self.failUnlessEqual(len(sequencing), 2)
 
-                  
 
 
-        
-    
+    def test_format_sample_sheet(self):
+        fcid = '42JU1AAXX'
+        url = '/experiments/config/%s/json' % (fcid,)
+        flowcell_request = self.client.get(url, apidata)
+        self.failUnlessEqual(flowcell_request.status_code, 200)
+        flowcell_info = json.loads(flowcell_request.content)
+
+        options = getCombinedOptions(['-f',fcid,'-g',os.getcwd(),])
+
+        output = StringIO()
+        save_sample_sheet(output, options, flowcell_info)
+        output.seek(0)
+        sheet = list(csv.DictReader(output))
+        expected = [{'SampleProject': '12044_index1', 'Index': 'ATCACG'},
+                    {'SampleProject': '12044_index2', 'Index': 'CGATGT'},
+                    {'SampleProject': '12044_index3', 'Index': 'TTAGGC'},
+                    {'SampleProject': '11045_index1', 'Index': 'ATCACG'},
+                    ]
+        for i in range(4):
+            self.assertEqual(sheet[i]['SampleProject'],
+                             expected[i]['SampleProject'])
+            self.assertEqual(sheet[i]['Index'],
+                             expected[i]['Index'])
+            self.assertEqual(sheet[i]['FCID'], fcid)
+            self.assertEqual(sheet[i]['Lane'], '3')
+
+
index e4fdff1fad42122e6a20b0f678f9660aed9c5809..1417baf3ac950ca7f8e63e0b77a4c4f3f066e341 100755 (executable)
@@ -1,4 +1,8 @@
 #!/usr/bin/env python
 #!/usr/bin/env python
+import os
+if not 'DJANGO_SETTINGS_MODULE' in os.environ:
+    os.environ['DJANGO_SETTINGS_MODULE'] = 'htsworkflow.settings'
+
 import logging
 import sys
 from htsworkflow.pipelines.retrieve_config import *
 import logging
 import sys
 from htsworkflow.pipelines.retrieve_config import *
@@ -7,37 +11,43 @@ from htsworkflow.pipelines import retrieve_config
 #Turn on built-in command-line parsing.
 retrieve_config.DISABLE_CMDLINE = False
 
 #Turn on built-in command-line parsing.
 retrieve_config.DISABLE_CMDLINE = False
 
+
 def main(argv=None):
 def main(argv=None):
-  if argv is None:
-    argv = sys.argv
-    
-  #Display help if no args are presented
-  options = getCombinedOptions(argv)
-
-  if options.verbose:
-    logging.basicConfig(level=logging.DEBUG)
-  else:
-    logging.basicConfig(level=logging.INFO)
-  
-  msg_list = ['ERROR MESSAGES:']
-  if options.flowcell is None:
-    msg_list.append("  Flow cell argument required. -f <flowcell> or --flowcell=<flowcell>")
-    
-  if options.url is None:
-    msg_list.append("  URL argument required (-u <url> or --url=<url>), or entry\n" \
-                    "    in /etc/ga_frontend/ga_frontend.conf or ~/.ga_frontend.conf")
-  if options.genome_dir is None:
-    msg_list.append("  genome_dir argument required (-g <genome_dir> or \n" \
-                    "    --genome_dir=<genome_dir>, or entry in \n" \
-                    "    /etc/ga_frontend/ga_frontend.conf or ~/.ga_frontend.conf")
-    
-  if len(msg_list) > 1:
-    print '\n'.join(msg_list)
-    return 1
-  
-  saveConfigFile(options)
-  
-  return 0
-  
+    if argv is None:
+        argv = sys.argv
+
+    #Display help if no args are presented
+    options = getCombinedOptions(argv)
+
+    if options.verbose:
+        logging.basicConfig(level=logging.DEBUG)
+    else:
+        logging.basicConfig(level=logging.INFO)
+
+    msg_list = ['ERROR MESSAGES:']
+    if options.flowcell is None:
+        msg_list.append(
+          "  Flow cell argument required. -f <flowcell> or "\
+          "--flowcell=<flowcell>")
+
+    if options.url is None:
+        msg_list.append(
+          "  URL argument required (-u <url> or --url=<url>), or entry\n" \
+          "    in /etc/ga_frontend/ga_frontend.conf or ~/.ga_frontend.conf")
+
+    if options.genome_dir is None:
+        msg_list.append(
+          "  genome_dir argument required (-g <genome_dir> or \n" \
+          "    --genome_dir=<genome_dir>, or entry in \n" \
+          "    /etc/ga_frontend/ga_frontend.conf or ~/.ga_frontend.conf")
+
+    if len(msg_list) > 1:
+        print '\n'.join(msg_list)
+        return 1
+
+    saveConfigFile(options)
+
+    return 0
+
 if __name__ == "__main__":
 if __name__ == "__main__":
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))