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