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 os
 import re
 
@@ -20,7 +20,7 @@ from htsworkflow.frontend.experiments.models import \
     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):
     """
@@ -33,13 +33,13 @@ def flowcell_information(flowcell_id):
 
     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,
-            '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,
@@ -48,12 +48,17 @@ def flowcell_information(flowcell_id):
             '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)
-        
+
     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,
     }
-    
+
     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)
-    
+
     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')
 
@@ -93,12 +98,12 @@ def lanes_for(username=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})
-        
+
     lanes = Lane.objects.filter(**query).order_by('-flowcell__run_date')
 
-    
+
     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
-    
+
     #convert query set to python structure
-    
+
     result_json = json.dumps(result)
     return HttpResponse(result_json, mimetype='application/json')
-                 
+
+
 def updStatus(request):
     output=''
     user = 'none'
@@ -141,7 +147,7 @@ def updStatus(request):
       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 ~~~~
@@ -149,22 +155,22 @@ def updStatus(request):
       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('updst'):
       UpdatedStatus = request.REQUEST['updst']
     else:
       return HttpResponse('missing status')
-    
-    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     # 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
@@ -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/'
-    
-    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 ]
-      
+
     except ObjectDoesNotExist:
       config = 'Entry not found for fcid  = '+fcid
 
@@ -245,8 +251,8 @@ def getConfile(req):
               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
 
@@ -264,14 +270,14 @@ def getLaneLibs(req):
     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__()
-        else: month = month.__str__() 
+        else: month = month.__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
-    
+
     cycles = flowcell.read_length
     if flowcell.paired_end:
         cycles *= 2
@@ -314,7 +320,7 @@ def 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
@@ -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)
-    
+
     return (estimate_low, estimate_high)
-    
+
 
 def makeUserLaneMap(flowcell):
     """
@@ -349,26 +355,26 @@ def makeUserLaneMap(flowcell):
 
 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)
-                
+
     return 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)
-                
+
     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": "",
@@ -18,7 +18,7 @@
    {"pk": 5, "model": "samples.htsuser",
     "fields" : {}
    },
-   {"pk": 6, "model": "auth.user", 
+   {"pk": 6, "model": "auth.user",
    "fields": {
        "username": "admintest",
        "first_name": "",
@@ -37,7 +37,7 @@
    {"pk": 6, "model": "samples.htsuser",
     "fields" : {}
    },
-   {"pk": 7, "model": "auth.user", 
+   {"pk": 7, "model": "auth.user",
    "fields": {
        "username": "supertest",
        "first_name": "",
          "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": {
-      "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,
       "sequencer": 2,
       "flowcell_id": "FC12150"
       }
-  }, 
+  },
   {"pk": 1193, "model": "experiments.lane",
    "fields": {
        "comment": "No change in cluster numbers, despite slight increase in pM",
        "lane_number": 1,
        "pM": "8"
        }
-   }, 
+   },
    {"pk": 1192, "model": "experiments.lane",
     "fields": {
         "comment": "Other library",
         "lane_number": 1,
         "pM": "7"
         }
-    }, 
+    },
 
-  {"pk": "10981", "model": "samples.library", 
+  {"pk": "10981", "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1194, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 2,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11016", 
-        "model": "samples.library", 
+        "pk": "11016",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1195, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 3,
        "pM": "7"
        }
-   },  
+   },
     {
-        "pk": "SL039", 
-        "model": "samples.library", 
+        "pk": "SL039",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"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"
        }
-   }, 
+   },
     {
-        "pk": "11060", 
-        "model": "samples.library", 
+        "pk": "11060",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1197, "model": "experiments.lane",
    "fields": {
        "comment": "stuff",
        "pM": "7",
        "status": 2
        }
-   }, 
+   },
     {
-        "pk": "11061", 
-        "model": "samples.library", 
+        "pk": "11061",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"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
        }
-   }, 
+   },
     {
-        "pk": "11062", 
-        "model": "samples.library", 
+        "pk": "11062",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1199, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 7,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11063", 
-        "model": "samples.library", 
+        "pk": "11063",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"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": {
-            "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
-            ], 
-            "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
         }
     },
-   {"pk": 152, "model": "experiments.flowcell", 
+   {"pk": 152, "model": "experiments.flowcell",
    "fields": {
        "paired_end": false,
        "run_date": "2009-09-10 18:30:15",
        "sequencer": 1,
        "flowcell_id": "42JTNAAXX"
        }
-   }, 
+   },
   {"pk": 1185, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
-        "pk": "11035", 
-        "model": "samples.library", 
+        "pk": "11035",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1186, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
-        "pk": "11037", 
-        "model": "samples.library", 
+        "pk": "11037",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1187, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
-        "pk": "11045", 
-        "model": "samples.library", 
+        "pk": "11045",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1188, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "pM": "7"}
    },
     {
-        "pk": "11046", 
-        "model": "samples.library", 
+        "pk": "11046",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    },   
+    },
   {"pk": 1189, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 5,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11054", 
-        "model": "samples.library", 
+        "pk": "11054",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1190, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
-        "pk": "11056", 
-        "model": "samples.library", 
+        "pk": "11056",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    },     
+    },
   {"pk": 1191, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 7,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11057", 
-        "model": "samples.library", 
+        "pk": "11057",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1192, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 8,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11065", 
-        "model": "samples.library", 
+        "pk": "11065",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 151, "model": "experiments.flowcell",
    "fields": {
        "paired_end": false,
        "lane_number": 1,
        "pM": "7"
        }
-   }, 
+   },
   {"pk": 1178, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 2,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11036", 
-        "model": "samples.library", 
+        "pk": "11036",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
     {
-        "pk": "11034", 
-        "model": "samples.library", 
+        "pk": "11034",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
     {
-        "pk": "11044", 
-        "model": "samples.library", 
+        "pk": "12044",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
+    {
+        "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": "",
-       "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"
        }
-   }, 
+   },
     {
-        "pk": "11044", 
-        "model": "samples.library", 
+        "pk": "11044",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1180, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 4,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11047", 
-        "model": "samples.library", 
+        "pk": "11047",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1181, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 5,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11055", 
-        "model": "samples.library", 
+        "pk": "11055",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1182, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 6,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11067", 
-        "model": "samples.library", 
+        "pk": "11067",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1183, "model": "experiments.lane",
    "fields": {
        "comment": "",
        "lane_number": 7,
        "pM": "7"
        }
-   }, 
+   },
     {
-        "pk": "11069", 
-        "model": "samples.library", 
+        "pk": "11069",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
-    }, 
+    },
   {"pk": 1184, "model": "experiments.lane",
    "fields": {
        "comment": "",
        }
    },
     {
-        "pk": "11070", 
-        "model": "samples.library", 
+        "pk": "11070",
+        "model": "samples.library",
         "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
-            ], 
-            "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
         }
      },
- {"pk": 200, "model": "experiments.flowcell", 
+ {"pk": 200, "model": "experiments.flowcell",
   "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,
index 664547bfb7e5d799fca4e4036eb6221762197af8..ef299e3676a02bbd087917d2bfc8654f3a391668 100644 (file)
@@ -19,7 +19,8 @@ from htsworkflow.frontend.auth import apidata
 
 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']
@@ -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))
-        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,
-                             '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'))
@@ -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)
-            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['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():
-                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['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.failUnlessAlmostEqual(float(lane_dict['pM']), float(lane.pM))
+                self.failUnlessAlmostEqual(float(lane_dict['pM']),
+                                           float(lane.pM))
                 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)
-            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['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():
-                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['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.failUnlessAlmostEqual(float(lane_dict['pM']), float(lane.pM))
+                self.failUnlessAlmostEqual(float(lane_dict['pM']),
+                                           float(lane.pM))
                 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
         """
-        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):
@@ -117,13 +137,16 @@ class ExperimentsTestCases(TestCase):
 
     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(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)
@@ -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
-        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)
-        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')
-            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])
@@ -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)
-        
-        
+
+    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):
         """
@@ -185,24 +220,29 @@ class ExperimentsTestCases(TestCase):
         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'])
-            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):
         """
-        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)
 
-        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):
@@ -212,22 +252,21 @@ class ExperimentsTestCases(TestCase):
         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)
 
-
     def test_raw_data_dir(self):
         """Raw data path generator check"""
         flowcell_id = self.fc1_id
         raw_dir = os.path.join(settings.RESULT_HOME_DIR, flowcell_id)
-        
+
         fc = models.FlowCell.objects.get(flowcell_id=flowcell_id)
         self.failUnlessEqual(fc.get_raw_data_directory(), raw_dir)
 
         fc.flowcell_id = flowcell_id + " (failed)"
         self.failUnlessEqual(fc.get_raw_data_directory(), raw_dir)
 
-
     def test_data_run_import(self):
         srf_file_type = models.FileType.objects.get(name='SRF')
         runxml_file_type = models.FileType.objects.get(name='run_xml')
@@ -254,10 +293,9 @@ class ExperimentsTestCases(TestCase):
         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)
-            
 
     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()
-        
-        #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:
-            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'
-            
+
             self.failUnlessEqual(mimetype, response['content-type'])
-        
+
+
 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))
-    
+
+
 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),
-                 ('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_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),
@@ -319,10 +359,10 @@ class TestFileType(TestCase):
                   '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),
-                 ('/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),
@@ -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))
-            self.failUnlessEqual(result.get('lane',None), lane)
+            self.failUnlessEqual(result.get('lane', None), lane)
             self.failUnlessEqual(result.get('end', None), end)
-                             
+
+
 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)
-        
+
     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):
-        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)
-        
+
         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:
@@ -373,11 +415,16 @@ class TestEmailNotify(TestCase):
         """
         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
-        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 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
@@ -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 }
@@ -76,7 +78,7 @@ class HTSUserCreationForm(UserCreationForm):
 class HTSUserChangeForm(UserChangeForm):
     class Meta:
         model = HTSUser
-        
+
 class HTSUserOptions(UserAdmin):
     form = HTSUserChangeForm
     add_form = HTSUserCreationForm
@@ -89,14 +91,19 @@ class Library_Inline(admin.TabularInline):
   model = Library
 
 class LibraryTypeOptions(admin.ModelAdmin):
+    list_display = ['name', 'is_paired_end', 'can_multiplex']
     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",)
             }
-        
+
     date_hierarchy = "creation_date"
     save_as = True
     save_on_top = True
@@ -110,7 +117,7 @@ class LibraryOptions(admin.ModelAdmin):
     list_display = (
         'id',
         'library_name',
-        'public',
+        'index_sequence_text',
         'affiliation',
         'undiluted_concentration',
         'gel_cut_size',
@@ -119,9 +126,9 @@ class LibraryOptions(admin.ModelAdmin):
     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',)
@@ -129,7 +136,8 @@ class LibraryOptions(admin.ModelAdmin):
       (None, {
         'fields': (
           ('id','library_name','hidden'),
-          ('library_species', 'library_type', 'experiment_type'),
+          ('library_species', 'experiment_type'),
+          ('library_type', 'multiplex_id'),
           )
          }),
          ('Experiment Detail:', {
@@ -140,12 +148,12 @@ class LibraryOptions(admin.ModelAdmin):
             '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_summary'), 
+                         ('bioanalyzer_summary'),
                          ('notes'))
          }),
          ('Library/Project Affiliation:', {
@@ -156,45 +164,45 @@ class LibraryOptions(admin.ModelAdmin):
       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.
         """
-        
+
         #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"
-        
+
         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_list = []
         #Iterate over selected labels to print
         for library in queryset.all():
-            
+
             # 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))
-        
+
         print_zpl_socket(zpl_list, host=template.printer.ip_address)
-    
+
         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):
@@ -220,7 +228,7 @@ class SpeciesOptions(admin.ModelAdmin):
 
 class TagOptions(admin.ModelAdmin):
     list_display = ('tag_name', 'context')
-    fieldsets = ( 
+    fieldsets = (
         (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(MultiplexIndex, MultiplexIndexOptions)
 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",
-        "notes": "Unknown"    
+        "notes": "Unknown"
      }
   },
   {
@@ -12,7 +12,7 @@
      "pk": 2,
      "fields": {
         "cellline_name": "C2C12 Exponential",
-        "notes": ""    
+        "notes": ""
      }
   },
   {
      "pk": 3,
      "fields": {
         "cellline_name": "C2C12 60 hrs",
-        "notes": "Unknown"    
+        "notes": "Unknown"
      }
   },
   {
      "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": {
-        "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"
      }
+  },
+  {
+    "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
-    # 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,
-        null=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,
-        null=True, 
+        null=True,
         db_index=True)
-    
+
     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,
-        null=True, 
+        null=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 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):
-  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)
@@ -96,7 +96,7 @@ class Species(models.Model):
 
   def __unicode__(self):
     return u'%s (%s)' % (self.scientific_name, self.common_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)])
-  
+
 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"
-  
+
   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):
@@ -127,11 +127,28 @@ class Affiliation(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):
-    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)
@@ -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) 
+                                                blank=True,null=True)
   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)
-  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")
-  
+
   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')
-  
+
   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,
-      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)
@@ -190,27 +211,59 @@ class Library(models.Model):
   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="")
-  
+
   def __unicode__(self):
     return u'#%s: %s' % (self.id, self.library_name)
-  
+
   class Meta:
       verbose_name_plural = "libraries"
-      #ordering = ["-creation_date"] 
+      #ordering = ["-creation_date"]
       ordering = ["-id"]
-  
+
   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
 
+  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 = ''
@@ -218,7 +271,7 @@ class Library(models.Model):
     for t in affs:
         ar.append(t.__unicode__())
     return '%s' % (", ".join(ar))
-    
+
   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
-      
+
 
   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):
-    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
 
@@ -273,21 +326,21 @@ class Library(models.Model):
         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>'
-    else: tstr = 'not processed yet' 
+    else: tstr = 'not processed yet'
     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
-    
+
   @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 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()
-    
+
 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">
-    <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/>
-    <b>Name</b>: 
+    <b>Name</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>
-    <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>:
@@ -47,7 +47,7 @@
     <br/>
     {% endif %}
     {% if lib.replicate %}
-    <b>Replicate</b>: 
+    <b>Replicate</b>:
        <span property="libns:replicate">{{ lib.replicate }}</span>
     <br/>
     {% endif %}
     <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>
-    <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 %}
-    <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 %}
-    <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 %}
-    <b>Concentration</b>: 
+    <b>Concentration</b>:
       <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
 
+import csv
 from ConfigParser import RawConfigParser
 import logging
 from optparse import OptionParser, IndentedHelpFormatter
 import os
 import sys
+import types
 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.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"
 
@@ -28,17 +34,15 @@ GERALD_CONFIG_SECTION = 'gerald_config'
 #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):
     """
-    Return a dictionary describing a 
+    Return a dictionary describing a
     """
     url = api.flowcell_url(base_host_url, flowcell)
-  
+
     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)
-    
+
     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 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
-    
+
 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 = {}
-    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):
@@ -103,10 +108,12 @@ def format_gerald_header(flowcell_info):
     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)
@@ -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)
-        
+
         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:
@@ -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 ) ]
 
-    # 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,) ]
-        
+
     config += [''] # force trailing newline
-    
+
     return "\n".join(config)
-              
+
 class DummyOptions:
   """
   Used when command line parsing is disabled; default
@@ -171,14 +178,14 @@ class DummyOptions:
     self.genome_dir = None
 
 class PreformattedDescriptionFormatter(IndentedHelpFormatter):
-  
+
   #def format_description(self, description):
-  #  
+  #
   #  if description:
   #      return description + "\n"
   #  else:
   #     return ""
-      
+
   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.epilog = """
 Config File:
   * %s (System wide)
   * %s (User specific; overrides system)
   * command line overrides all config file options
-  
+
   Example Config File:
-  
+
     [%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)
-  
+
     #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("-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 ")
 
+    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
-    
+
 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)
-  
+
     return parser
 
 
@@ -264,13 +278,13 @@ def getCombinedOptions(argv=None):
         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')
-      
+
     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')
@@ -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')
-            
+
     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),))
-   
+
   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() ),))
 
-  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:
-      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
+from StringIO import StringIO
 
 try:
     import json
 except ImportError, e:
     import simplejson as json
-    
+
 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']
@@ -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)
-
-        print dir(flowcell_request)
         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' }
-        
+
         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)
 
-                  
 
-        
-    
+    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
+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 *
@@ -7,37 +11,43 @@ from htsworkflow.pipelines import retrieve_config
 #Turn on built-in command-line parsing.
 retrieve_config.DISABLE_CMDLINE = False
 
+
 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__":
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))