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