5cf2e9097ee1693ddcb15b0306b0ed33ed654bb8
[htsworkflow.git] / samples / models.py
1 import types
2 import logging
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
9 logger = logging.getLogger(__name__)
10
11 class Antibody(models.Model):
12     antigene = models.CharField(max_length=500, db_index=True)
13     # New field Aug/20/08
14     # SQL to add column:
15     # alter table fctracker_antibody add column "nickname" varchar(20) NULL;
16     nickname = models.CharField(
17         max_length=20,
18         blank=True,
19         null=True,
20         db_index=True
21     )
22     catalog = models.CharField(max_length=50, blank=True, null=True)
23     antibodies = models.CharField(max_length=500, db_index=True)
24     source = models.CharField(max_length=500, blank=True, null=True, db_index=True)
25     biology = models.TextField(blank=True, null=True)
26     notes = models.TextField(blank=True, null=True)
27     def __unicode__(self):
28         return u'%s - %s' % (self.antigene, self.antibodies)
29     class Meta:
30         verbose_name_plural = "antibodies"
31         ordering = ["antigene"]
32
33 class Cellline(models.Model):
34     cellline_name = models.CharField(max_length=100, unique=True, db_index=True)
35     nickname = models.CharField(max_length=20,
36         blank=True,
37         null=True,
38         db_index=True)
39
40     notes = models.TextField(blank=True)
41     def __unicode__(self):
42         return unicode(self.cellline_name)
43
44     class Meta:
45         ordering = ["cellline_name"]
46
47 class Condition(models.Model):
48     condition_name = models.CharField(
49         max_length=2000, unique=True, db_index=True)
50     nickname = models.CharField(max_length=20,
51         blank=True,
52         null=True,
53         db_index=True,
54         verbose_name = 'Short Name')
55     notes = models.TextField(blank=True)
56
57     def __unicode__(self):
58         return unicode(self.condition_name)
59
60     class Meta:
61         ordering = ["condition_name"]
62
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 ('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                           verbose_name="Adapter Type")
131   is_paired_end = models.BooleanField(default=True,
132                     help_text="can you do a paired end run with this adapter")
133   can_multiplex = models.BooleanField(default=True,
134                     help_text="Does this adapter provide multiplexing?")
135
136   def __unicode__(self):
137       return unicode(self.name)
138
139   class Meta:
140       ordering = ["-id"]
141
142
143 class MultiplexIndex(models.Model):
144     """Map adapter types to the multiplex sequence"""
145     adapter_type = models.ForeignKey(LibraryType)
146     multiplex_id = models.CharField(max_length=6, null=False)
147     sequence = models.CharField(max_length=12, blank=True, null=True)
148
149     class Meta:
150         verbose_name_plural = "multiplex indicies"
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(default=False)
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(default=False)
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_expressions = self.multiplex_id.split(',')
248       for multiplex_term in multiplex_expressions:
249           pairs = multiplex_term.split('-')
250           if len(pairs) == 1:
251               key = pairs[0]
252               seq = self._lookup_index(pairs[0])
253           elif len(pairs) == 2:
254               key = pairs[0] + '-' + pairs[1]
255               seq0 = self._lookup_index(pairs[0])
256               seq1 = self._lookup_index(pairs[1])
257               if seq0 is None or seq1 is None:
258                   seq = None
259               else:
260                   seq = seq0 + '-' + seq1
261           else:
262               raise RuntimeError("Too many - seperated sequences")
263           if seq is None:
264               seq = 'Err: index not found'
265           sequences[key] = seq
266       return sequences
267
268   def _lookup_index(self, multiplex_id):
269       try:
270           multiplex = MultiplexIndex.objects.get(
271               adapter_type = self.library_type.id,
272               multiplex_id = multiplex_id)
273           return multiplex.sequence
274       except MultiplexIndex.DoesNotExist as e:
275           return None
276
277   def index_sequence_text(self, seperator=' '):
278       """Return formatted multiplex index sequences"""
279       sequences = self.index_sequences()
280       if sequences is None:
281           return ""
282       if type(sequences) in types.StringTypes:
283           return sequences
284       multiplex_ids = sequences.keys()
285       multiplex_ids.sort()
286       return seperator.join(( "%s:%s" %(i,sequences[i]) for i in multiplex_ids))
287   index_sequence_text.short_description = "Index"
288
289
290   def affiliation(self):
291     affs = self.affiliations.all().order_by('name')
292     tstr = ''
293     ar = []
294     for t in affs:
295         ar.append(t.__unicode__())
296     return '%s' % (", ".join(ar))
297
298   def is_archived(self):
299     """
300     returns True if archived else False
301     """
302     if self.longtermstorage_set.count() > 0:
303         return True
304     else:
305         return False
306
307   def stopping_point_name(self):
308       end_points = Library.PROTOCOL_END_POINTS_DICT
309       name = end_points.get(self.stopping_point, None)
310       if name is None:
311           name = "Lookup Error"
312           logger.error("protocol stopping point in database didn't match names in library model")
313       return name
314
315
316   def libtags(self):
317     affs = self.tags.all().order_by('tag_name')
318     ar = []
319     for t in affs:
320       ar.append(t.__unicode__())
321     return u'%s' % ( ", ".join(ar))
322
323   def DataRun(self):
324     str ='<a target=_self href="/admin/experiments/datarun/?q='+self.id+'" title="Check All Data Runs for This Specific Library ..." ">Data Run</a>'
325     return str
326   DataRun.allow_tags = True
327
328   def aligned_m_reads(self):
329     return getLibReads(self.id)
330
331   def aligned_reads(self):
332     res = getLibReads(self.id)
333
334     # Check data sanity
335     if res[2] != "OK":
336       return u'<div style="border:solid red 2px">'+res[2]+'</div>'
337
338     rc = "%1.2f" % (res[1]/1000000.0)
339     # 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
340     if res[0] > 0:
341       bgcolor = '#ff3300'  # Red
342       rc_thr = [10000000,5000000,3000000]
343       if self.experiment_type == 'RNA-seq':
344         rc_thr = [20000000,10000000,6000000]
345
346       if res[1] > rc_thr[0]:
347         bgcolor = '#66ff66'  # Green
348       else:
349         if res[1] > rc_thr[1]:
350           bgcolor ='#00ccff'  # Blue
351         else:
352            if res[1] > rc_thr[2]:
353              bgcolor ='#ffcc33'  # Orange
354       tstr = '<div style="background-color:'+bgcolor+';color:black">'
355       tstr += res[0].__unicode__()+' Lanes, '+rc+' M Reads'
356       tstr += '</div>'
357     else: tstr = 'not processed yet'
358     return tstr
359   aligned_reads.allow_tags = True
360
361   def public(self):
362     SITE_ROOT = '/'
363     summary_url = self.get_absolute_url()
364     return '<a href="%s">S</a>' % (summary_url,)
365   public.allow_tags = True
366
367   @models.permalink
368   def get_absolute_url(self):
369     return ('samples.views.library_to_flowcells', [str(self.id)])
370
371   def get_admin_url(self):
372       return urlresolvers.reverse('admin:samples_library_change',
373                                   args=(self.id,))
374
375 class HTSUser(User):
376     """
377     Provide some site-specific customization for the django user class
378     """
379     #objects = UserManager()
380
381     class Meta:
382         ordering = ['first_name', 'last_name', 'username']
383
384     def admin_url(self):
385         return '/admin/%s/%s/%d' % (self._meta.app_label, self._meta.module_name, self.id)
386
387     def __unicode__(self):
388         #return unicode(self.username) + u" (" + unicode(self.get_full_name()) + u")"
389         return unicode(self.get_full_name()) + u' (' + unicode(self.username) + ')'
390
391 def HTSUserInsertID(sender, instance, **kwargs):
392     """
393     Force addition of HTSUsers when someone just modifies the auth_user object
394     """
395     u = HTSUser.objects.filter(pk=instance.id)
396     if len(u) == 0:
397         cursor = connection.cursor()
398         cursor.execute('INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' % (instance.id,))
399         cursor.close()
400
401 post_save.connect(HTSUserInsertID, sender=User)