Merge ssh://jumpgate.caltech.edu/var/htsworkflow/htsworkflow
[htsworkflow.git] / htsworkflow / frontend / samples / models.py
1 import logging
2 import urlparse
3 from django.db import models
4 from django.contrib.auth.models import User, UserManager
5 from django.core import urlresolvers
6 from django.db.models.signals import pre_save, post_save
7 from django.db import connection
8 from htsworkflow.frontend.reports.libinfopar import *
9
10 logger = logging.getLogger(__name__)
11
12 class Antibody(models.Model):
13     antigene = models.CharField(max_length=500, db_index=True)
14     # New field Aug/20/08
15     # SQL to add column:
16     # alter table fctracker_antibody add column "nickname" varchar(20) NULL;
17     nickname = models.CharField(
18         max_length=20,
19         blank=True,
20         null=True,
21         db_index=True
22     )
23     catalog = models.CharField(max_length=50, blank=True, null=True)
24     antibodies = models.CharField(max_length=500, db_index=True)
25     source = models.CharField(max_length=500, blank=True, null=True, db_index=True)
26     biology = models.TextField(blank=True, null=True)
27     notes = models.TextField(blank=True, null=True)
28     def __unicode__(self):
29         return u'%s - %s' % (self.antigene, self.antibodies)
30     class Meta:
31         verbose_name_plural = "antibodies"
32         ordering = ["antigene"]
33
34 class Cellline(models.Model):
35     cellline_name = models.CharField(max_length=100, unique=True, db_index=True)
36     nickname = models.CharField(max_length=20,
37         blank=True,
38         null=True,
39         db_index=True)
40
41     notes = models.TextField(blank=True)
42     def __unicode__(self):
43         return unicode(self.cellline_name)
44
45     class Meta:
46         ordering = ["cellline_name"]
47
48 class Condition(models.Model):
49     condition_name = models.CharField(
50         max_length=2000, unique=True, db_index=True)
51     nickname = models.CharField(max_length=20,
52         blank=True,
53         null=True,
54         db_index=True,
55         verbose_name = 'Short Name')
56     notes = models.TextField(blank=True)
57
58     def __unicode__(self):
59         return unicode(self.condition_name)
60
61     class Meta:
62         ordering = ["condition_name"]
63
64
65 class ExperimentType(models.Model):
66   name = models.CharField(max_length=50, unique=True)
67
68   def __unicode__(self):
69     return unicode(self.name)
70
71 class Tag(models.Model):
72   tag_name = models.CharField(max_length=100, db_index=True,blank=False,null=False)
73   TAG_CONTEXT = (
74       #('Antibody','Antibody'),
75       #('Cellline', 'Cellline'),
76       #('Condition', 'Condition'),
77       ('Library', 'Library'),
78       ('ANY','ANY'),
79   )
80   context = models.CharField(max_length=50,
81       choices=TAG_CONTEXT, default='Library')
82
83   def __unicode__(self):
84     return u'%s' % (self.tag_name)
85
86   class Meta:
87     ordering = ["context","tag_name"]
88
89 class Species(models.Model):
90   scientific_name = models.CharField(max_length=256,
91       unique=False,
92       db_index=True
93   )
94   common_name = models.CharField(max_length=256, blank=True)
95   #use_genome_build = models.CharField(max_length=100, blank=False, null=False)
96
97   def __unicode__(self):
98     return u'%s (%s)' % (self.scientific_name, self.common_name)
99
100   class Meta:
101     verbose_name_plural = "species"
102     ordering = ["scientific_name"]
103
104   @models.permalink
105   def get_absolute_url(self):
106     return ('htsworkflow.frontend.samples.views.species', [str(self.id)])
107
108 class Affiliation(models.Model):
109   name = models.CharField(max_length=256, db_index=True, verbose_name='Name')
110   contact = models.CharField(max_length=256, null=True, blank=True,verbose_name='Lab Name')
111   email = models.EmailField(null=True,blank=True)
112   users = models.ManyToManyField('HTSUser', null=True, blank=True)
113   users.admin_order_field = "username"
114
115   def __unicode__(self):
116     str = unicode(self.name)
117     if self.contact is not None and len(self.contact) > 0:
118       str += u' ('+self.contact+u')'
119     return str
120
121   def Users(self):
122       users = self.users.all().order_by('username')
123       return ", ".join([unicode(a) for a in users ])
124
125   class Meta:
126     ordering = ["name","contact"]
127     unique_together = (("name", "contact"),)
128
129 class LibraryType(models.Model):
130   name = models.CharField(max_length=255, unique=True,
131                           name="Adapter Type")
132   is_paired_end = models.BooleanField(default=True,
133                     help_text="can you do a paired end run with this adapter")
134   can_multiplex = models.BooleanField(default=True,
135                     help_text="Does this adapter provide multiplexing?")
136
137   def __unicode__(self):
138       return unicode(self.name)
139
140   class Meta:
141       ordering = ["-id"]
142
143
144 class MultiplexIndex(models.Model):
145     """Map adapter types to the multiplex sequence"""
146     adapter_type = models.ForeignKey(LibraryType)
147     multiplex_id = models.CharField(max_length=6, null=False)
148     sequence = models.CharField(max_length=12, blank=True, null=True)
149
150     class Meta:
151         verbose_name_plural = "multiplex indicies"
152         unique_together = ('adapter_type', 'multiplex_id')
153
154 class Library(models.Model):
155   id = models.CharField(max_length=10, primary_key=True)
156   library_name = models.CharField(max_length=100, unique=True)
157   library_species = models.ForeignKey(Species)
158   hidden = models.BooleanField()
159   account_number = models.CharField(max_length=100, null=True, blank=True)
160   cell_line = models.ForeignKey(Cellline, blank=True, null=True,
161                                 verbose_name="Background")
162   condition = models.ForeignKey(Condition, blank=True, null=True)
163   antibody = models.ForeignKey(Antibody,blank=True,null=True)
164   affiliations = models.ManyToManyField(
165       Affiliation,related_name='library_affiliations',null=True)
166   tags = models.ManyToManyField(Tag,related_name='library_tags',
167                                 blank=True,null=True)
168   REPLICATE_NUM = [(x,x) for x in range(1,7)]
169   replicate =  models.PositiveSmallIntegerField(choices=REPLICATE_NUM,
170                                                 blank=True,null=True)
171   experiment_type = models.ForeignKey(ExperimentType)
172   library_type = models.ForeignKey(LibraryType, blank=True, null=True,
173                                    verbose_name="Adapter Type")
174   multiplex_id = models.CharField(max_length=128,
175                                   blank=True, null=True,
176                                   verbose_name="Index ID")
177   creation_date = models.DateField(blank=True, null=True)
178   made_for = models.CharField(max_length=50, blank=True,
179                               verbose_name='ChIP/DNA/RNA Made By')
180   made_by = models.CharField(max_length=50, blank=True, default="Lorian")
181
182   PROTOCOL_END_POINTS = (
183       ('?', 'Unknown'),
184       ('Sample', 'Raw sample'),
185       ('Progress', 'In progress'),
186       ('1A', 'Ligation, then gel'),
187       ('PCR', 'Ligation, then PCR'),
188       ('1Ab', 'Ligation, PCR, then gel'),
189       ('1Ac', 'Ligation, gel, then 12x PCR'),
190       ('1Aa', 'Ligation, gel, then 18x PCR'),
191       ('2A', 'Ligation, PCR, gel, PCR'),
192       ('Done', 'Completed'),
193     )
194   PROTOCOL_END_POINTS_DICT = dict(PROTOCOL_END_POINTS)
195   stopping_point = models.CharField(max_length=25,
196                                     choices=PROTOCOL_END_POINTS,
197                                     default='Done')
198
199   amplified_from_sample = models.ForeignKey('self',
200                             related_name='amplified_into_sample',
201                             blank=True, null=True)
202
203   undiluted_concentration = models.DecimalField("Concentration",
204       max_digits=5, decimal_places=2, blank=True, null=True,
205       help_text=u"Undiluted concentration (ng/\u00b5l)")
206       # note \u00b5 is the micro symbol in unicode
207   successful_pM = models.DecimalField(max_digits=9,
208                                       decimal_places=1, blank=True, null=True)
209   ten_nM_dilution = models.BooleanField()
210   gel_cut_size = models.IntegerField(default=225, blank=True, null=True)
211   insert_size = models.IntegerField(blank=True, null=True)
212   notes = models.TextField(blank=True)
213
214   bioanalyzer_summary = models.TextField(blank=True,default="")
215   bioanalyzer_concentration = models.DecimalField(max_digits=5,
216                                 decimal_places=2, blank=True, null=True,
217                                 help_text=u"(ng/\u00b5l)")
218   bioanalyzer_image_url = models.URLField(blank=True,default="")
219
220   def __unicode__(self):
221     return u'#%s: %s' % (self.id, self.library_name)
222
223   class Meta:
224       verbose_name_plural = "libraries"
225       #ordering = ["-creation_date"]
226       ordering = ["-id"]
227
228   def antibody_name(self):
229     str ='<a target=_self href="/admin/samples/antibody/'+self.antibody.id.__str__()+'/" title="'+self.antibody.__str__()+'">'+self.antibody.label+'</a>'
230     return str
231   antibody_name.allow_tags = True
232
233   def organism(self):
234     return self.library_species.common_name
235
236   def index_sequences(self):
237       """Return a dictionary of multiplex index id to sequence
238       Return None if the library can't multiplex,
239
240       """
241       if self.library_type is None:
242           return None
243       if not self.library_type.can_multiplex:
244           return None
245       if self.multiplex_id is None or len(self.multiplex_id) == 0:
246           return 'Err: id empty'
247       sequences = {}
248       multiplex_expressions = self.multiplex_id.split(',')
249       for multiplex_term in multiplex_expressions:
250           pairs = multiplex_term.split('-')
251           if len(pairs) == 1:
252               key = pairs[0]
253               seq = self._lookup_index(pairs[0])
254           elif len(pairs) == 2:
255               key = pairs[0] + '-' + pairs[1]
256               seq0 = self._lookup_index(pairs[0])
257               seq1 = self._lookup_index(pairs[1])
258               if seq0 is None or seq1 is None:
259                   seq = None
260               else:
261                   seq = seq0 + '-' + seq1
262           else:
263               raise RuntimeError("Too many - seperated sequences")
264           if seq is None:
265               seq = 'Err: index not found'
266           sequences[key] = seq
267       return sequences
268
269   def _lookup_index(self, multiplex_id):
270       try:
271           multiplex = MultiplexIndex.objects.get(
272               adapter_type = self.library_type.id,
273               multiplex_id = multiplex_id)
274           return multiplex.sequence
275       except MultiplexIndex.DoesNotExist, e:
276           return None
277
278   def index_sequence_text(self, seperator=' '):
279       """Return formatted multiplex index sequences"""
280       sequences = self.index_sequences()
281       if sequences is None:
282           return ""
283       multiplex_ids = sequences.keys()
284       multiplex_ids.sort()
285       return seperator.join(( "%s:%s" %(i,sequences[i]) for i in multiplex_ids))
286   index_sequence_text.short_description = "Index"
287
288
289   def affiliation(self):
290     affs = self.affiliations.all().order_by('name')
291     tstr = ''
292     ar = []
293     for t in affs:
294         ar.append(t.__unicode__())
295     return '%s' % (", ".join(ar))
296
297   def is_archived(self):
298     """
299     returns True if archived else False
300     """
301     if self.longtermstorage_set.count() > 0:
302         return True
303     else:
304         return False
305
306   def stopping_point_name(self):
307       end_points = Library.PROTOCOL_END_POINTS_DICT
308       name = end_points.get(self.stopping_point, None)
309       if name is None:
310           name = "Lookup Error"
311           logger.error("protocol stopping point in database didn't match names in library model")
312       return name
313
314
315   def libtags(self):
316     affs = self.tags.all().order_by('tag_name')
317     ar = []
318     for t in affs:
319       ar.append(t.__unicode__())
320     return u'%s' % ( ", ".join(ar))
321
322   def DataRun(self):
323     str ='<a target=_self href="/admin/experiments/datarun/?q='+self.id+'" title="Check All Data Runs for This Specific Library ..." ">Data Run</a>'
324     return str
325   DataRun.allow_tags = True
326
327   def aligned_m_reads(self):
328     return getLibReads(self.id)
329
330   def aligned_reads(self):
331     res = getLibReads(self.id)
332
333     # Check data sanity
334     if res[2] != "OK":
335       return u'<div style="border:solid red 2px">'+res[2]+'</div>'
336
337     rc = "%1.2f" % (res[1]/1000000.0)
338     # Color Scheme: green is more than 10M, blue is more than 5M, orange is more than 3M and red is less. For RNAseq, all those thresholds should be doubled
339     if res[0] > 0:
340       bgcolor = '#ff3300'  # Red
341       rc_thr = [10000000,5000000,3000000]
342       if self.experiment_type == 'RNA-seq':
343         rc_thr = [20000000,10000000,6000000]
344
345       if res[1] > rc_thr[0]:
346         bgcolor = '#66ff66'  # Green
347       else:
348         if res[1] > rc_thr[1]:
349           bgcolor ='#00ccff'  # Blue
350         else:
351            if res[1] > rc_thr[2]:
352              bgcolor ='#ffcc33'  # Orange
353       tstr = '<div style="background-color:'+bgcolor+';color:black">'
354       tstr += res[0].__unicode__()+' Lanes, '+rc+' M Reads'
355       tstr += '</div>'
356     else: tstr = 'not processed yet'
357     return tstr
358   aligned_reads.allow_tags = True
359
360   def public(self):
361     SITE_ROOT = '/'
362     summary_url = self.get_absolute_url()
363     return '<a href="%s">S</a>' % (summary_url,)
364   public.allow_tags = True
365
366   @models.permalink
367   def get_absolute_url(self):
368     return ('htsworkflow.frontend.samples.views.library_to_flowcells', [str(self.id)])
369
370   def get_admin_url(self):
371       return urlresolvers.reverse('admin:samples_library_change',
372                                   args=(self.id,))
373
374 class HTSUser(User):
375     """
376     Provide some site-specific customization for the django user class
377     """
378     #objects = UserManager()
379
380     class Meta:
381         ordering = ['first_name', 'last_name', 'username']
382
383     def admin_url(self):
384         return '/admin/%s/%s/%d' % (self._meta.app_label, self._meta.module_name, self.id)
385
386     def __unicode__(self):
387         #return unicode(self.username) + u" (" + unicode(self.get_full_name()) + u")"
388         return unicode(self.get_full_name()) + u' (' + unicode(self.username) + ')'
389
390 def HTSUserInsertID(sender, instance, **kwargs):
391     """
392     Force addition of HTSUsers when someone just modifies the auth_user object
393     """
394     u = HTSUser.objects.filter(pk=instance.id)
395     if len(u) == 0:
396         cursor = connection.cursor()
397         cursor.execute('INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' % (instance.id,))
398         cursor.close()
399
400 post_save.connect(HTSUserInsertID, sender=User)