1 from __future__ import unicode_literals
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 from django.core.validators import RegexValidator
12 logger = logging.getLogger(__name__)
15 class AccessionAgency(models.Model):
16 """An organization one submits data to
18 name = models.CharField(max_length=255)
19 homepage = models.URLField(blank=True)
20 library_template = models.URLField(blank=True)
23 verbose_name_plural = 'Accession Agencies'
29 class Accession(models.Model):
30 """Track accession IDs assigned to our objects.
32 accession = models.CharField(
35 validators=[RegexValidator(
37 message="Please only use letters, digits, and :.-")]
39 url = models.URLField(blank=True, null=True)
40 agency = models.ForeignKey(AccessionAgency)
41 created = models.DateTimeField()
46 def update_url(self, template):
47 if template and not self.url:
48 self.url = template.format(self.accession)
51 return str(self.agency) + ":" + self.accession
54 class LibraryAccession(Accession):
55 library = models.ForeignKey('Library')
57 def save(self, *args, **kwargs):
58 self.update_url(self.agency.library_template)
59 super(LibraryAccession, self).save(*args, **kwargs)
62 class Antibody(models.Model):
63 antigene = models.CharField(max_length=500, db_index=True)
66 # alter table fctracker_antibody add column "nickname" varchar(20) NULL;
67 nickname = models.CharField(
73 catalog = models.CharField(max_length=50, blank=True, null=True)
74 antibodies = models.CharField(max_length=500, db_index=True)
75 source = models.CharField(max_length=500,
76 blank=True, null=True, db_index=True)
77 biology = models.TextField(blank=True, null=True)
78 notes = models.TextField(blank=True, null=True)
81 return '%s - %s' % (self.antigene, self.antibodies)
84 verbose_name_plural = "antibodies"
85 ordering = ["antigene"]
88 class Cellline(models.Model):
89 cellline_name = models.CharField(max_length=100,
90 unique=True, db_index=True)
91 nickname = models.CharField(
97 notes = models.TextField(blank=True)
100 return str(self.cellline_name)
103 ordering = ["cellline_name"]
106 class Condition(models.Model):
107 condition_name = models.CharField(
108 max_length=2000, unique=True, db_index=True)
109 nickname = models.CharField(
114 verbose_name='Short Name')
115 notes = models.TextField(blank=True)
118 return str(self.condition_name)
121 ordering = ["condition_name"]
124 class ExperimentType(models.Model):
125 name = models.CharField(max_length=50, unique=True)
128 return str(self.name)
131 class Tag(models.Model):
132 tag_name = models.CharField(max_length=100,
133 db_index=True, blank=False, null=False)
135 # ('Antibody','Antibody'),
136 # ('Cellline', 'Cellline'),
137 # ('Condition', 'Condition'),
138 ('Library', 'Library'),
141 context = models.CharField(
143 choices=TAG_CONTEXT, default='Library')
146 return '%s' % (self.tag_name,)
149 ordering = ["context", "tag_name"]
152 class Species(models.Model):
153 scientific_name = models.CharField(
158 common_name = models.CharField(max_length=256, blank=True)
161 return '%s (%s)' % (self.scientific_name, self.common_name)
164 verbose_name_plural = "species"
165 ordering = ["scientific_name"]
168 def get_absolute_url(self):
169 return ('samples.views.species', [str(self.id)])
172 class Affiliation(models.Model):
173 name = models.CharField(max_length=256, db_index=True, verbose_name='Name')
174 contact = models.CharField(max_length=256,
175 null=True, blank=True, verbose_name='Lab Name')
176 email = models.EmailField(null=True, blank=True)
177 users = models.ManyToManyField('HTSUser', null=True, blank=True)
178 users.admin_order_field = "username"
181 name = str(self.name)
182 if self.contact is not None and len(self.contact) > 0:
183 name += ' ('+self.contact+')'
187 users = self.users.all().order_by('username')
188 return ", ".join([str(a) for a in users])
191 ordering = ["name", "contact"]
192 unique_together = (("name", "contact"),)
195 class LibraryType(models.Model):
196 name = models.CharField(max_length=255, unique=True,
197 verbose_name="Adapter Type")
198 is_paired_end = models.BooleanField(
200 help_text="can you do a paired end run with this adapter")
201 can_multiplex = models.BooleanField(
203 help_text="Does this adapter provide multiplexing?")
206 return str(self.name)
212 class MultiplexIndex(models.Model):
213 """Map adapter types to the multiplex sequence"""
214 adapter_type = models.ForeignKey(LibraryType)
215 multiplex_id = models.CharField(max_length=6, null=False)
216 sequence = models.CharField(max_length=12, blank=True, null=True)
219 verbose_name_plural = "multiplex indicies"
220 unique_together = ('adapter_type', 'multiplex_id')
223 class Library(models.Model):
224 id = models.CharField(max_length=10, primary_key=True)
225 library_name = models.CharField(max_length=100, unique=True)
226 library_species = models.ForeignKey(Species)
227 hidden = models.BooleanField(default=False)
228 account_number = models.CharField(max_length=100, null=True, blank=True)
229 cell_line = models.ForeignKey(Cellline, blank=True, null=True,
230 verbose_name="Background")
231 condition = models.ForeignKey(Condition, blank=True, null=True)
232 antibody = models.ForeignKey(Antibody, blank=True, null=True)
233 affiliations = models.ManyToManyField(
234 Affiliation, related_name='library_affiliations', null=True)
235 tags = models.ManyToManyField(Tag, related_name='library_tags',
236 blank=True, null=True)
237 REPLICATE_NUM = [(x, x) for x in range(1, 7)]
238 replicate = models.PositiveSmallIntegerField(choices=REPLICATE_NUM,
239 blank=True, null=True)
240 experiment_type = models.ForeignKey(ExperimentType)
241 library_type = models.ForeignKey(LibraryType, blank=True, null=True,
242 verbose_name="Adapter Type")
243 multiplex_id = models.CharField(max_length=128,
244 blank=True, null=True,
245 verbose_name="Index ID")
246 creation_date = models.DateField(blank=True, null=True)
247 made_for = models.CharField(max_length=50, blank=True,
248 verbose_name='ChIP/DNA/RNA Made By')
249 made_by = models.CharField(max_length=50, blank=True, default="Lorian")
251 PROTOCOL_END_POINTS = (
253 ('Sample', 'Raw sample'),
254 ('Progress', 'In progress'),
255 ('1A', 'Ligation, then gel'),
256 ('PCR', 'Ligation, then PCR'),
257 ('1Ab', 'Ligation, PCR, then gel'),
258 ('1Ac', 'Ligation, gel, then 12x PCR'),
259 ('1Aa', 'Ligation, gel, then 18x PCR'),
260 ('2A', 'Ligation, PCR, gel, PCR'),
261 ('Done', 'Completed'),
263 PROTOCOL_END_POINTS_DICT = dict(PROTOCOL_END_POINTS)
264 stopping_point = models.CharField(max_length=25,
265 choices=PROTOCOL_END_POINTS,
268 amplified_from_sample = models.ForeignKey(
270 related_name='amplified_into_sample',
271 blank=True, null=True)
273 undiluted_concentration = models.DecimalField(
275 max_digits=5, decimal_places=2, blank=True, null=True,
276 help_text="Undiluted concentration (ng/\u00b5l)")
277 # note \u00b5 is the micro symbol in unicode
278 successful_pM = models.DecimalField(
279 max_digits=9, decimal_places=1, blank=True, null=True)
280 ten_nM_dilution = models.BooleanField(default=False)
281 gel_cut_size = models.IntegerField(default=225, blank=True, null=True)
282 insert_size = models.IntegerField(blank=True, null=True)
283 notes = models.TextField(blank=True)
285 bioanalyzer_summary = models.TextField(blank=True, default="")
286 bioanalyzer_concentration = models.DecimalField(
287 max_digits=5, decimal_places=2, blank=True, null=True,
288 help_text="(ng/\u00b5l)")
289 bioanalyzer_image_url = models.URLField(blank=True, default="")
292 return '#%s: %s' % (self.id, self.library_name)
295 verbose_name_plural = "libraries"
296 # ordering = ["-creation_date"]
299 def antibody_name(self):
300 str = '<a target=_self href="/admin/samples/antibody/' + \
301 self.antibody.id.__str__() + \
302 '/" title="' + self.antibody.__str__() + '">' + \
303 self.antibody.label+'</a>'
305 antibody_name.allow_tags = True
308 return self.library_species.common_name
310 def index_sequences(self):
311 """Return a dictionary of multiplex index id to sequence
312 Return None if the library can't multiplex,
314 if self.library_type is None:
316 if not self.library_type.can_multiplex:
318 if self.multiplex_id is None or len(self.multiplex_id) == 0:
319 return 'Err: id empty'
321 multiplex_expressions = self.multiplex_id.split(',')
322 for multiplex_term in multiplex_expressions:
323 pairs = multiplex_term.split('-')
326 seq = self._lookup_index(pairs[0])
327 elif len(pairs) == 2:
328 key = pairs[0] + '-' + pairs[1]
329 seq0 = self._lookup_index(pairs[0])
330 seq1 = self._lookup_index(pairs[1])
331 if seq0 is None or seq1 is None:
334 seq = seq0 + '-' + seq1
336 raise RuntimeError("Too many - seperated sequences")
338 seq = 'Err: index not found'
342 def _lookup_index(self, multiplex_id):
344 multiplex = MultiplexIndex.objects.get(
345 adapter_type=self.library_type.id,
346 multiplex_id=multiplex_id)
347 return multiplex.sequence
348 except MultiplexIndex.DoesNotExist as e:
351 def index_sequence_text(self, seperator=' '):
352 """Return formatted multiplex index sequences"""
353 sequences = self.index_sequences()
354 if sequences is None:
356 if isinstance(sequences, six.string_types):
358 multiplex_ids = sorted(sequences)
359 return seperator.join(
360 ("%s:%s" % (i, sequences[i]) for i in multiplex_ids))
361 index_sequence_text.short_description = "Index"
363 def affiliation(self):
364 affs = self.affiliations.all().order_by('name')
368 ar.append(t.__str__())
369 return '%s' % (", ".join(ar))
371 def is_archived(self):
372 """returns True if archived else False
374 if self.longtermstorage_set.count() > 0:
379 def lanes_sequenced(self):
380 """Count how many lanes of each type were run.
387 counts = [[0, 0, 0], [0, 0, 0]]
389 for lane in self.lane_set.all():
390 if lane.flowcell.paired_end:
395 if lane.flowcell.read_length < 40:
396 read_type = short_read
397 elif lane.flowcell.read_length < 100:
398 read_type = medium_read
400 read_type = long_read
401 counts[lane_type][read_type] += 1
405 def stopping_point_name(self):
406 end_points = Library.PROTOCOL_END_POINTS_DICT
407 name = end_points.get(self.stopping_point, None)
409 name = "Lookup Error"
410 logger.error("protocol stopping point in database"
411 "didn't match names in library model")
415 affs = self.tags.all().order_by('tag_name')
418 ar.append(t.__str__())
419 return '%s' % (", ".join(ar))
422 str = '<a target=_self href="/admin/experiments/datarun/?q=' + \
424 '" title="Check All Data Runs for This Specific Library ..."' \
427 DataRun.allow_tags = True
429 def aligned_m_reads(self):
430 return getLibReads(self.id)
432 def aligned_reads(self):
433 res = getLibReads(self.id)
437 return '<div style="border:solid red 2px">'+res[2]+'</div>'
439 rc = "%1.2f" % (res[1]/1000000.0)
440 # 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
442 bgcolor = '#ff3300' # Red
443 rc_thr = [10000000, 5000000, 3000000]
444 if self.experiment_type == 'RNA-seq':
445 rc_thr = [20000000, 10000000, 6000000]
447 if res[1] > rc_thr[0]:
448 bgcolor = '#66ff66' # Green
450 if res[1] > rc_thr[1]:
451 bgcolor = '#00ccff' # Blue
453 if res[1] > rc_thr[2]:
454 bgcolor = '#ffcc33' # Orange
455 tstr = '<div style="background-color:'+bgcolor+';color:black">'
456 tstr += res[0].__str__()+' Lanes, '+rc+' M Reads'
459 tstr = 'not processed yet'
461 aligned_reads.allow_tags = True
464 summary_url = self.get_absolute_url()
465 return '<a href="%s">S</a>' % (summary_url,)
466 public.allow_tags = True
469 def get_absolute_url(self):
470 return ('samples.views.library_to_flowcells', [str(self.id)])
472 def get_admin_url(self):
473 return urlresolvers.reverse('admin:samples_library_change',
479 Provide some site-specific customization for the django user class
481 # objects = UserManager()
484 ordering = ['first_name', 'last_name', 'username']
487 return '/admin/%s/%s/%d' % (self._meta.app_label,
488 self._meta.module_name, self.id)
491 # return str(self.username) + " (" + str(self.get_full_name()) + u")"
492 return str(self.get_full_name()) + ' (' + str(self.username) + ')'
495 def HTSUserInsertID(sender, instance, **kwargs):
497 Force addition of HTSUsers when someone just modifies the auth_user object
499 u = HTSUser.objects.filter(pk=instance.id)
501 cursor = connection.cursor()
503 'INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' %
507 post_save.connect(HTSUserInsertID, sender=User)