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