Add support for tracking the multiplex index sequence.
[htsworkflow.git] / htsworkflow / frontend / samples / models.py
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)