Convert Rami's DataRun feature to something useful to us.
[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, unique=True, db_index=True)
24     antibodies = models.CharField(max_length=500, db_index=True)
25     source = models.CharField(max_length=500, blank=True, db_index=True)
26     biology = models.TextField(blank=True)
27     notes = models.TextField(blank=True)
28     def __unicode__(self):
29         return u'%s - %s (%s)' % (self.antigene, self.antibodies, self.catalog)
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 class ExperimentType(models.Model):
65   name = models.CharField(max_length=50, unique=True)
66
67   def __unicode__(self):
68     return unicode(self.name)
69
70 class Tag(models.Model): 
71   tag_name = models.CharField(max_length=100, db_index=True,blank=False,null=False) 
72   TAG_CONTEXT = ( 
73       #('Antibody','Antibody'), 
74       #('Cellline', 'Cellline'), 
75       #('Condition', 'Condition'), 
76       ('Library', 'Library'), 
77       ('ANY','ANY'), 
78   ) 
79   context = models.CharField(max_length=50, 
80       choices=TAG_CONTEXT, default='Library') 
81  
82   def __unicode__(self): 
83     return u'%s' % (self.tag_name) 
84  
85   class Meta: 
86     ordering = ["context","tag_name"] 
87  
88 class Species(models.Model):
89   scientific_name = models.CharField(max_length=256, 
90       unique=False, 
91       db_index=True
92   )
93   common_name = models.CharField(max_length=256, blank=True)
94   #use_genome_build = models.CharField(max_length=100, blank=False, null=False)
95
96   def __unicode__(self):
97     return u'%s (%s)' % (self.scientific_name, self.common_name)
98   
99   class Meta:
100     verbose_name_plural = "species"
101     ordering = ["scientific_name"]
102
103   @models.permalink
104   def get_absolute_url(self):
105     return ('htsworkflow.frontend.samples.views.species', [str(self.id)])
106   
107 class Affiliation(models.Model):
108   name = models.CharField(max_length=256, db_index=True, verbose_name='Name')
109   contact = models.CharField(max_length=256, null=True, blank=True,verbose_name='Lab Name')  
110   email = models.EmailField(null=True,blank=True)
111   users = models.ManyToManyField('HTSUser', null=True, blank=True)
112   users.admin_order_field = "username"
113   
114   def __unicode__(self):
115     str = unicode(self.name)
116     if self.contact is not None and len(self.contact) > 0:
117       str += u' ('+self.contact+u')' 
118     return str
119
120   def Users(self):
121       users = self.users.all().order_by('username')
122       return ", ".join([unicode(a) for a in users ])
123
124   class Meta:
125     ordering = ["name","contact"]
126     unique_together = (("name", "contact"),)
127
128 class LibraryType(models.Model):
129   name = models.CharField(max_length=255, unique=True)
130
131   def __unicode__(self):
132     return unicode(self.name)
133
134
135 class Library(models.Model):
136   id = models.CharField(max_length=10, primary_key=True)
137   library_name = models.CharField(max_length=100, unique=True)
138   library_species = models.ForeignKey(Species)
139   # new field 2008 Mar 5, alter table samples_library add column "hidden" NOT NULL default 0;
140   hidden = models.BooleanField()
141   # new field 2009 Oct 6, alter table samples_library add column "account_number" varchar(100) NULL
142   account_number = models.CharField(max_length=100, null=True, blank=True)
143   cell_line = models.ForeignKey(Cellline, blank=True, null=True, verbose_name="Background")
144   condition = models.ForeignKey(Condition, blank=True, null=True)
145   antibody = models.ForeignKey(Antibody,blank=True,null=True)
146   # New field Aug/25/08. SQL: alter table fctracker_library add column "lib_affiliation" varchar(256)  NULL;
147   affiliations = models.ManyToManyField(Affiliation,related_name='library_affiliations',null=True)
148   # new field Nov/14/08
149   tags = models.ManyToManyField(Tag,related_name='library_tags',blank=True,null=True)
150   # New field Aug/19/08
151   # SQL to add column: alter table fctracker_library add column "replicate" smallint unsigned NULL;
152   REPLICATE_NUM = ((1,1),(2,2),(3,3),(4,4))
153   replicate =  models.PositiveSmallIntegerField(choices=REPLICATE_NUM,blank=True,null=True) 
154   experiment_type = models.ForeignKey(ExperimentType)
155   library_type = models.ForeignKey(LibraryType, blank=True, null=True)
156   creation_date = models.DateField(blank=True, null=True)
157   made_for = models.CharField(max_length=50, blank=True, 
158       verbose_name='ChIP/DNA/RNA Made By')
159   made_by = models.CharField(max_length=50, blank=True, default="Lorian")
160   
161   PROTOCOL_END_POINTS = (
162       ('?', 'Unknown'),
163       ('Sample', 'Raw sample'),
164       ('Progress', 'In progress'),
165       ('1A', 'Ligation, then gel'),
166       ('PCR', 'Ligation, then PCR'),
167       ('1Ab', 'Ligation, PCR, then gel'),
168       ('1Ac', 'Ligation, gel, then 12x PCR'),
169       ('1Aa', 'Ligation, gel, then 18x PCR'),
170       ('2A', 'Ligation, PCR, gel, PCR'),
171       ('Done', 'Completed'),
172     )
173   PROTOCOL_END_POINTS_DICT = dict(PROTOCOL_END_POINTS)
174   
175   stopping_point = models.CharField(max_length=25, choices=PROTOCOL_END_POINTS, default='Done')
176   amplified_from_sample = models.ForeignKey('self', blank=True, null=True, related_name='amplified_into_sample')  
177   
178   undiluted_concentration = models.DecimalField("Concentration", 
179       max_digits=5, decimal_places=2, blank=True, null=True,
180       help_text=u"Undiluted concentration (ng/\u00b5l)") 
181       # note \u00b5 is the micro symbol in unicode
182   successful_pM = models.DecimalField(max_digits=9, decimal_places=1, blank=True, null=True)
183   ten_nM_dilution = models.BooleanField()
184   gel_cut_size = models.IntegerField(default=225, blank=True, null=True)
185   insert_size = models.IntegerField(blank=True, null=True)
186   notes = models.TextField(blank=True)
187
188   bioanalyzer_summary = models.TextField(blank=True,default="")
189   bioanalyzer_concentration = models.DecimalField(max_digits=5, 
190                                 decimal_places=2, blank=True, null=True,
191                                 help_text=u"(ng/\u00b5l)")
192   bioanalyzer_image_url = models.URLField(blank=True,default="")
193   
194   def __unicode__(self):
195     return u'#%s: %s' % (self.id, self.library_name)
196   
197   class Meta:
198       verbose_name_plural = "libraries"
199       #ordering = ["-creation_date"] 
200       ordering = ["-id"]
201   
202   def antibody_name(self):
203     str ='<a target=_self href="/admin/samples/antibody/'+self.antibody.id.__str__()+'/" title="'+self.antibody.__str__()+'">'+self.antibody.label+'</a>' 
204     return str
205   antibody_name.allow_tags = True
206
207   def organism(self):
208     return self.library_species.common_name
209
210   def affiliation(self):
211     affs = self.affiliations.all().order_by('name')
212     tstr = ''
213     ar = []
214     for t in affs:
215         ar.append(t.__unicode__())
216     return '%s' % (", ".join(ar))
217     
218   def is_archived(self):
219     """
220     returns True if archived else False
221     """
222     if self.longtermstorage_set.count() > 0:
223         return True
224     else:
225         return False
226
227   def stopping_point_name(self):
228       end_points = Library.PROTOCOL_END_POINTS_DICT
229       name = end_points.get(self.stopping_point, None)
230       if name is None:
231           name = "Lookup Error"
232           logger.error("protocol stopping point in database didn't match names in library model")
233       return name
234       
235
236   def libtags(self):
237     affs = self.tags.all().order_by('tag_name')
238     ar = []
239     for t in affs:
240       ar.append(t.__unicode__())
241     return u'%s' % ( ", ".join(ar))
242
243   def DataRun(self):
244     str ='<a target=_self href="/admin/experiments/datarun/?q='+self.id+'" title="Check All Data Runs for This Specific Library ..." ">Data Run</a>' 
245     return str
246   DataRun.allow_tags = True
247
248   def aligned_m_reads(self):
249     return getLibReads(self.id)
250
251   def aligned_reads(self):
252     res = getLibReads(self.id)
253
254     # Check data sanity
255     if res[2] != "OK":
256       return u'<div style="border:solid red 2px">'+res[2]+'</div>'
257
258     rc = "%1.2f" % (res[1]/1000000.0)
259     # 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
260     if res[0] > 0:
261       bgcolor = '#ff3300'  # Red
262       rc_thr = [10000000,5000000,3000000]
263       if self.experiment_type == 'RNA-seq':
264         rc_thr = [20000000,10000000,6000000]
265
266       if res[1] > rc_thr[0]:
267         bgcolor = '#66ff66'  # Green
268       else:
269         if res[1] > rc_thr[1]:
270           bgcolor ='#00ccff'  # Blue
271         else:
272            if res[1] > rc_thr[2]: 
273              bgcolor ='#ffcc33'  # Orange
274       tstr = '<div style="background-color:'+bgcolor+';color:black">'
275       tstr += res[0].__unicode__()+' Lanes, '+rc+' M Reads'
276       tstr += '</div>'
277     else: tstr = 'not processed yet' 
278     return tstr
279   aligned_reads.allow_tags = True
280   
281   def public(self):
282     SITE_ROOT = '/'
283     summary_url = self.get_absolute_url()
284     return '<a href="%s">S</a>' % (summary_url,)
285   public.allow_tags = True
286     
287   @models.permalink
288   def get_absolute_url(self):
289     return ('htsworkflow.frontend.samples.views.library_to_flowcells', [str(self.id)])
290
291   def get_admin_url(self):
292       return urlresolvers.reverse('admin:samples_library_change',
293                                   args=(self.id,))
294
295 class HTSUser(User):
296     """
297     Provide some site-specific customization for the django user class
298     """
299     #objects = UserManager()
300
301     class Meta:
302         ordering = ['first_name', 'last_name', 'username']
303
304     def admin_url(self):
305         return '/admin/%s/%s/%d' % (self._meta.app_label, self._meta.module_name, self.id)
306
307     def __unicode__(self):
308         #return unicode(self.username) + u" (" + unicode(self.get_full_name()) + u")"
309         return unicode(self.get_full_name()) + u' (' + unicode(self.username) + ')'
310     
311 def HTSUserInsertID(sender, instance, **kwargs):
312     """
313     Force addition of HTSUsers when someone just modifies the auth_user object
314     """
315     u = HTSUser.objects.filter(pk=instance.id)
316     if len(u) == 0:
317         cursor = connection.cursor()
318         cursor.execute('INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' % (instance.id,))
319         cursor.close()
320     
321 post_save.connect(HTSUserInsertID, sender=User)