Function generating index text was assuming there'd always be an index
[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=3, null=False)
148     sequence = models.CharField(max_length=12, blank=True, null=True)
149
150     class Meta:
151         unique_together = ('adapter_type', 'multiplex_id')
152
153 class Library(models.Model):
154   id = models.CharField(max_length=10, primary_key=True)
155   library_name = models.CharField(max_length=100, unique=True)
156   library_species = models.ForeignKey(Species)
157   hidden = models.BooleanField()
158   account_number = models.CharField(max_length=100, null=True, blank=True)
159   cell_line = models.ForeignKey(Cellline, blank=True, null=True,
160                                 verbose_name="Background")
161   condition = models.ForeignKey(Condition, blank=True, null=True)
162   antibody = models.ForeignKey(Antibody,blank=True,null=True)
163   affiliations = models.ManyToManyField(
164       Affiliation,related_name='library_affiliations',null=True)
165   tags = models.ManyToManyField(Tag,related_name='library_tags',
166                                 blank=True,null=True)
167   REPLICATE_NUM = [(x,x) for x in range(1,7)]
168   replicate =  models.PositiveSmallIntegerField(choices=REPLICATE_NUM,
169                                                 blank=True,null=True)
170   experiment_type = models.ForeignKey(ExperimentType)
171   library_type = models.ForeignKey(LibraryType, blank=True, null=True,
172                                    verbose_name="Adapter Type")
173   multiplex_id = models.CharField(max_length=128,
174                                   blank=True, null=True,
175                                   verbose_name="Index ID")
176   creation_date = models.DateField(blank=True, null=True)
177   made_for = models.CharField(max_length=50, blank=True,
178                               verbose_name='ChIP/DNA/RNA Made By')
179   made_by = models.CharField(max_length=50, blank=True, default="Lorian")
180
181   PROTOCOL_END_POINTS = (
182       ('?', 'Unknown'),
183       ('Sample', 'Raw sample'),
184       ('Progress', 'In progress'),
185       ('1A', 'Ligation, then gel'),
186       ('PCR', 'Ligation, then PCR'),
187       ('1Ab', 'Ligation, PCR, then gel'),
188       ('1Ac', 'Ligation, gel, then 12x PCR'),
189       ('1Aa', 'Ligation, gel, then 18x PCR'),
190       ('2A', 'Ligation, PCR, gel, PCR'),
191       ('Done', 'Completed'),
192     )
193   PROTOCOL_END_POINTS_DICT = dict(PROTOCOL_END_POINTS)
194   stopping_point = models.CharField(max_length=25,
195                                     choices=PROTOCOL_END_POINTS,
196                                     default='Done')
197
198   amplified_from_sample = models.ForeignKey('self',
199                             related_name='amplified_into_sample',
200                             blank=True, null=True)
201
202   undiluted_concentration = models.DecimalField("Concentration",
203       max_digits=5, decimal_places=2, blank=True, null=True,
204       help_text=u"Undiluted concentration (ng/\u00b5l)")
205       # note \u00b5 is the micro symbol in unicode
206   successful_pM = models.DecimalField(max_digits=9,
207                                       decimal_places=1, blank=True, null=True)
208   ten_nM_dilution = models.BooleanField()
209   gel_cut_size = models.IntegerField(default=225, blank=True, null=True)
210   insert_size = models.IntegerField(blank=True, null=True)
211   notes = models.TextField(blank=True)
212
213   bioanalyzer_summary = models.TextField(blank=True,default="")
214   bioanalyzer_concentration = models.DecimalField(max_digits=5,
215                                 decimal_places=2, blank=True, null=True,
216                                 help_text=u"(ng/\u00b5l)")
217   bioanalyzer_image_url = models.URLField(blank=True,default="")
218
219   def __unicode__(self):
220     return u'#%s: %s' % (self.id, self.library_name)
221
222   class Meta:
223       verbose_name_plural = "libraries"
224       #ordering = ["-creation_date"]
225       ordering = ["-id"]
226
227   def antibody_name(self):
228     str ='<a target=_self href="/admin/samples/antibody/'+self.antibody.id.__str__()+'/" title="'+self.antibody.__str__()+'">'+self.antibody.label+'</a>'
229     return str
230   antibody_name.allow_tags = True
231
232   def organism(self):
233     return self.library_species.common_name
234
235   def index_sequences(self):
236       """Return a dictionary of multiplex index id to sequence
237       Return None if the library can't multiplex,
238
239       """
240       if self.library_type is None:
241           return None
242       if not self.library_type.can_multiplex:
243           return None
244       if self.multiplex_id is None or len(self.multiplex_id) == 0:
245           return 'Err: id empty'
246       sequences = {}
247       multiplex_ids = self.multiplex_id.split(',')
248       for multiplex_id in multiplex_ids:
249           try:
250               multiplex = MultiplexIndex.objects.get(
251                   adapter_type = self.library_type.id,
252                   multiplex_id = multiplex_id)
253               sequences[multiplex_id] = multiplex.sequence
254           except MultiplexIndex.DoesNotExist, e:
255               sequences[multiplex_id] = 'Err: index not found'
256       return sequences
257
258   def index_sequence_text(self, seperator=' '):
259       """Return formatted multiplex index sequences"""
260       sequences = self.index_sequences()
261       if sequences is None:
262           return ""
263
264       multiplex_ids = sequences.keys()
265       multiplex_ids.sort()
266       return seperator.join(( "%s:%s" %(i,sequences[i]) for i in multiplex_ids))
267   index_sequence_text.short_description = "Index"
268
269
270   def affiliation(self):
271     affs = self.affiliations.all().order_by('name')
272     tstr = ''
273     ar = []
274     for t in affs:
275         ar.append(t.__unicode__())
276     return '%s' % (", ".join(ar))
277
278   def is_archived(self):
279     """
280     returns True if archived else False
281     """
282     if self.longtermstorage_set.count() > 0:
283         return True
284     else:
285         return False
286
287   def stopping_point_name(self):
288       end_points = Library.PROTOCOL_END_POINTS_DICT
289       name = end_points.get(self.stopping_point, None)
290       if name is None:
291           name = "Lookup Error"
292           logger.error("protocol stopping point in database didn't match names in library model")
293       return name
294
295
296   def libtags(self):
297     affs = self.tags.all().order_by('tag_name')
298     ar = []
299     for t in affs:
300       ar.append(t.__unicode__())
301     return u'%s' % ( ", ".join(ar))
302
303   def DataRun(self):
304     str ='<a target=_self href="/admin/experiments/datarun/?q='+self.id+'" title="Check All Data Runs for This Specific Library ..." ">Data Run</a>'
305     return str
306   DataRun.allow_tags = True
307
308   def aligned_m_reads(self):
309     return getLibReads(self.id)
310
311   def aligned_reads(self):
312     res = getLibReads(self.id)
313
314     # Check data sanity
315     if res[2] != "OK":
316       return u'<div style="border:solid red 2px">'+res[2]+'</div>'
317
318     rc = "%1.2f" % (res[1]/1000000.0)
319     # 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
320     if res[0] > 0:
321       bgcolor = '#ff3300'  # Red
322       rc_thr = [10000000,5000000,3000000]
323       if self.experiment_type == 'RNA-seq':
324         rc_thr = [20000000,10000000,6000000]
325
326       if res[1] > rc_thr[0]:
327         bgcolor = '#66ff66'  # Green
328       else:
329         if res[1] > rc_thr[1]:
330           bgcolor ='#00ccff'  # Blue
331         else:
332            if res[1] > rc_thr[2]:
333              bgcolor ='#ffcc33'  # Orange
334       tstr = '<div style="background-color:'+bgcolor+';color:black">'
335       tstr += res[0].__unicode__()+' Lanes, '+rc+' M Reads'
336       tstr += '</div>'
337     else: tstr = 'not processed yet'
338     return tstr
339   aligned_reads.allow_tags = True
340
341   def public(self):
342     SITE_ROOT = '/'
343     summary_url = self.get_absolute_url()
344     return '<a href="%s">S</a>' % (summary_url,)
345   public.allow_tags = True
346
347   @models.permalink
348   def get_absolute_url(self):
349     return ('htsworkflow.frontend.samples.views.library_to_flowcells', [str(self.id)])
350
351   def get_admin_url(self):
352       return urlresolvers.reverse('admin:samples_library_change',
353                                   args=(self.id,))
354
355 class HTSUser(User):
356     """
357     Provide some site-specific customization for the django user class
358     """
359     #objects = UserManager()
360
361     class Meta:
362         ordering = ['first_name', 'last_name', 'username']
363
364     def admin_url(self):
365         return '/admin/%s/%s/%d' % (self._meta.app_label, self._meta.module_name, self.id)
366
367     def __unicode__(self):
368         #return unicode(self.username) + u" (" + unicode(self.get_full_name()) + u")"
369         return unicode(self.get_full_name()) + u' (' + unicode(self.username) + ')'
370
371 def HTSUserInsertID(sender, instance, **kwargs):
372     """
373     Force addition of HTSUsers when someone just modifies the auth_user object
374     """
375     u = HTSUser.objects.filter(pk=instance.id)
376     if len(u) == 0:
377         cursor = connection.cursor()
378         cursor.execute('INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' % (instance.id,))
379         cursor.close()
380
381 post_save.connect(HTSUserInsertID, sender=User)