Remove unused imports
[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/'+self.antibody.id.__str__()+'/" title="'+self.antibody.__str__()+'">'+self.antibody.label+'</a>'
254         return str
255     antibody_name.allow_tags = True
256
257     def organism(self):
258         return self.library_species.common_name
259
260     def index_sequences(self):
261         """Return a dictionary of multiplex index id to sequence
262         Return None if the library can't multiplex,
263         """
264         if self.library_type is None:
265             return None
266         if not self.library_type.can_multiplex:
267             return None
268         if self.multiplex_id is None or len(self.multiplex_id) == 0:
269             return 'Err: id empty'
270         sequences = {}
271         multiplex_expressions = self.multiplex_id.split(',')
272         for multiplex_term in multiplex_expressions:
273             pairs = multiplex_term.split('-')
274             if len(pairs) == 1:
275                 key = pairs[0]
276                 seq = self._lookup_index(pairs[0])
277             elif len(pairs) == 2:
278                 key = pairs[0] + '-' + pairs[1]
279                 seq0 = self._lookup_index(pairs[0])
280                 seq1 = self._lookup_index(pairs[1])
281                 if seq0 is None or seq1 is None:
282                     seq = None
283                 else:
284                     seq = seq0 + '-' + seq1
285             else:
286                 raise RuntimeError("Too many - seperated sequences")
287             if seq is None:
288                 seq = 'Err: index not found'
289             sequences[key] = seq
290         return sequences
291
292     def _lookup_index(self, multiplex_id):
293         try:
294             multiplex = MultiplexIndex.objects.get(
295                 adapter_type=self.library_type.id,
296                 multiplex_id=multiplex_id)
297             return multiplex.sequence
298         except MultiplexIndex.DoesNotExist as e:
299             return None
300
301     def index_sequence_text(self, seperator=' '):
302         """Return formatted multiplex index sequences"""
303         sequences = self.index_sequences()
304         if sequences is None:
305             return ""
306         if isinstance(sequences, six.string_types):
307             return sequences
308         multiplex_ids = sorted(sequences)
309         return seperator.join(("%s:%s" % (i,sequences[i]) for i in multiplex_ids))
310     index_sequence_text.short_description = "Index"
311
312     def affiliation(self):
313         affs = self.affiliations.all().order_by('name')
314         tstr = ''
315         ar = []
316         for t in affs:
317             ar.append(t.__str__())
318         return '%s' % (", ".join(ar))
319
320     def is_archived(self):
321         """returns True if archived else False
322         """
323         if self.longtermstorage_set.count() > 0:
324             return True
325         else:
326             return False
327
328     def lanes_sequenced(self):
329         """Count how many lanes of each type were run.
330         """
331         single = 0
332         paired = 1
333         short_read = 0
334         medium_read = 1
335         long_read = 2
336         counts = [[0, 0, 0], [0, 0, 0]]
337
338         for lane in self.lane_set.all():
339             if lane.flowcell.paired_end:
340                 lane_type = paired
341             else:
342                 lane_type = single
343
344             if lane.flowcell.read_length < 40:
345                 read_type = short_read
346             elif lane.flowcell.read_length < 100:
347                 read_type = medium_read
348             else:
349                 read_type = long_read
350             counts[lane_type][read_type] += 1
351
352         return counts
353
354     def stopping_point_name(self):
355         end_points = Library.PROTOCOL_END_POINTS_DICT
356         name = end_points.get(self.stopping_point, None)
357         if name is None:
358             name = "Lookup Error"
359             logger.error("protocol stopping point in database didn't match names in library model")
360         return name
361
362     def libtags(self):
363         affs = self.tags.all().order_by('tag_name')
364         ar = []
365         for t in affs:
366             ar.append(t.__str__())
367         return '%s' % (", ".join(ar))
368
369     def DataRun(self):
370         str ='<a target=_self href="/admin/experiments/datarun/?q='+self.id+'" title="Check All Data Runs for This Specific Library ..." ">Data Run</a>'
371         return str
372     DataRun.allow_tags = True
373
374     def aligned_m_reads(self):
375         return getLibReads(self.id)
376
377     def aligned_reads(self):
378         res = getLibReads(self.id)
379
380         # Check data sanity
381         if res[2] != "OK":
382             return '<div style="border:solid red 2px">'+res[2]+'</div>'
383
384         rc = "%1.2f" % (res[1]/1000000.0)
385         # 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
386         if res[0] > 0:
387             bgcolor = '#ff3300'  # Red
388             rc_thr = [10000000, 5000000, 3000000]
389             if self.experiment_type == 'RNA-seq':
390                 rc_thr = [20000000, 10000000, 6000000]
391
392             if res[1] > rc_thr[0]:
393                 bgcolor = '#66ff66'  # Green
394             else:
395                 if res[1] > rc_thr[1]:
396                     bgcolor = '#00ccff'  # Blue
397                 else:
398                     if res[1] > rc_thr[2]:
399                         bgcolor = '#ffcc33'  # Orange
400             tstr = '<div style="background-color:'+bgcolor+';color:black">'
401             tstr += res[0].__str__()+' Lanes, '+rc+' M Reads'
402             tstr += '</div>'
403         else:
404             tstr = 'not processed yet'
405         return tstr
406     aligned_reads.allow_tags = True
407
408     def public(self):
409         summary_url = self.get_absolute_url()
410         return '<a href="%s">S</a>' % (summary_url,)
411     public.allow_tags = True
412
413     @models.permalink
414     def get_absolute_url(self):
415         return ('samples.views.library_to_flowcells', [str(self.id)])
416
417     def get_admin_url(self):
418         return urlresolvers.reverse('admin:samples_library_change',
419                                     args=(self.id,))
420
421
422 class HTSUser(User):
423     """
424     Provide some site-specific customization for the django user class
425     """
426     #objects = UserManager()
427
428     class Meta:
429         ordering = ['first_name', 'last_name', 'username']
430
431     def admin_url(self):
432         return '/admin/%s/%s/%d' % (self._meta.app_label, self._meta.module_name, self.id)
433
434     def __str__(self):
435         # return str(self.username) + " (" + str(self.get_full_name()) + u")"
436         return str(self.get_full_name()) + ' (' + str(self.username) + ')'
437
438 def HTSUserInsertID(sender, instance, **kwargs):
439     """
440     Force addition of HTSUsers when someone just modifies the auth_user object
441     """
442     u = HTSUser.objects.filter(pk=instance.id)
443     if len(u) == 0:
444         cursor = connection.cursor()
445         cursor.execute('INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' % (instance.id,))
446         cursor.close()
447
448 post_save.connect(HTSUserInsertID, sender=User)