Convert old style except blocks to except Exception as variable: blocks
[htsworkflow.git] / 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
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 ('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                           verbose_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(default=False)
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(default=False)
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 as 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       if type(sequences) in types.StringTypes:
284           return sequences
285       multiplex_ids = sequences.keys()
286       multiplex_ids.sort()
287       return seperator.join(( "%s:%s" %(i,sequences[i]) for i in multiplex_ids))
288   index_sequence_text.short_description = "Index"
289
290
291   def affiliation(self):
292     affs = self.affiliations.all().order_by('name')
293     tstr = ''
294     ar = []
295     for t in affs:
296         ar.append(t.__unicode__())
297     return '%s' % (", ".join(ar))
298
299   def is_archived(self):
300     """
301     returns True if archived else False
302     """
303     if self.longtermstorage_set.count() > 0:
304         return True
305     else:
306         return False
307
308   def stopping_point_name(self):
309       end_points = Library.PROTOCOL_END_POINTS_DICT
310       name = end_points.get(self.stopping_point, None)
311       if name is None:
312           name = "Lookup Error"
313           logger.error("protocol stopping point in database didn't match names in library model")
314       return name
315
316
317   def libtags(self):
318     affs = self.tags.all().order_by('tag_name')
319     ar = []
320     for t in affs:
321       ar.append(t.__unicode__())
322     return u'%s' % ( ", ".join(ar))
323
324   def DataRun(self):
325     str ='<a target=_self href="/admin/experiments/datarun/?q='+self.id+'" title="Check All Data Runs for This Specific Library ..." ">Data Run</a>'
326     return str
327   DataRun.allow_tags = True
328
329   def aligned_m_reads(self):
330     return getLibReads(self.id)
331
332   def aligned_reads(self):
333     res = getLibReads(self.id)
334
335     # Check data sanity
336     if res[2] != "OK":
337       return u'<div style="border:solid red 2px">'+res[2]+'</div>'
338
339     rc = "%1.2f" % (res[1]/1000000.0)
340     # 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
341     if res[0] > 0:
342       bgcolor = '#ff3300'  # Red
343       rc_thr = [10000000,5000000,3000000]
344       if self.experiment_type == 'RNA-seq':
345         rc_thr = [20000000,10000000,6000000]
346
347       if res[1] > rc_thr[0]:
348         bgcolor = '#66ff66'  # Green
349       else:
350         if res[1] > rc_thr[1]:
351           bgcolor ='#00ccff'  # Blue
352         else:
353            if res[1] > rc_thr[2]:
354              bgcolor ='#ffcc33'  # Orange
355       tstr = '<div style="background-color:'+bgcolor+';color:black">'
356       tstr += res[0].__unicode__()+' Lanes, '+rc+' M Reads'
357       tstr += '</div>'
358     else: tstr = 'not processed yet'
359     return tstr
360   aligned_reads.allow_tags = True
361
362   def public(self):
363     SITE_ROOT = '/'
364     summary_url = self.get_absolute_url()
365     return '<a href="%s">S</a>' % (summary_url,)
366   public.allow_tags = True
367
368   @models.permalink
369   def get_absolute_url(self):
370     return ('samples.views.library_to_flowcells', [str(self.id)])
371
372   def get_admin_url(self):
373       return urlresolvers.reverse('admin:samples_library_change',
374                                   args=(self.id,))
375
376 class HTSUser(User):
377     """
378     Provide some site-specific customization for the django user class
379     """
380     #objects = UserManager()
381
382     class Meta:
383         ordering = ['first_name', 'last_name', 'username']
384
385     def admin_url(self):
386         return '/admin/%s/%s/%d' % (self._meta.app_label, self._meta.module_name, self.id)
387
388     def __unicode__(self):
389         #return unicode(self.username) + u" (" + unicode(self.get_full_name()) + u")"
390         return unicode(self.get_full_name()) + u' (' + unicode(self.username) + ')'
391
392 def HTSUserInsertID(sender, instance, **kwargs):
393     """
394     Force addition of HTSUsers when someone just modifies the auth_user object
395     """
396     u = HTSUser.objects.filter(pk=instance.id)
397     if len(u) == 0:
398         cursor = connection.cursor()
399         cursor.execute('INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' % (instance.id,))
400         cursor.close()
401
402 post_save.connect(HTSUserInsertID, sender=User)