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