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"]
167 def get_absolute_url(self):
168 return urlresolvers.reverse('species', kwargs={'species_id': str(self.id)})
171 class Affiliation(models.Model):
172 name = models.CharField(max_length=256, db_index=True, verbose_name='Name')
173 contact = models.CharField(max_length=256,
174 null=True, blank=True, verbose_name='Lab Name')
175 email = models.EmailField(null=True, blank=True)
176 users = models.ManyToManyField('HTSUser', null=True, blank=True)
177 users.admin_order_field = "username"
180 name = str(self.name)
181 if self.contact is not None and len(self.contact) > 0:
182 name += ' ('+self.contact+')'
186 users = self.users.all().order_by('username')
187 return ", ".join([str(a) for a in users])
190 ordering = ["name", "contact"]
191 unique_together = (("name", "contact"),)
194 class LibraryType(models.Model):
195 name = models.CharField(max_length=255, unique=True,
196 verbose_name="Adapter Type")
197 is_paired_end = models.BooleanField(
199 help_text="can you do a paired end run with this adapter")
200 can_multiplex = models.BooleanField(
202 help_text="Does this adapter provide multiplexing?")
205 return str(self.name)
211 class MultiplexIndex(models.Model):
212 """Map adapter types to the multiplex sequence"""
213 adapter_type = models.ForeignKey(LibraryType)
214 multiplex_id = models.CharField(max_length=6, null=False)
215 sequence = models.CharField(max_length=12, blank=True, null=True)
218 verbose_name_plural = "multiplex indicies"
219 unique_together = ('adapter_type', 'multiplex_id')
222 class Library(models.Model):
223 id = models.CharField(max_length=10, primary_key=True)
224 library_name = models.CharField(max_length=100, unique=True)
225 library_species = models.ForeignKey(Species)
226 hidden = models.BooleanField(default=False)
227 account_number = models.CharField(max_length=100, null=True, blank=True)
228 cell_line = models.ForeignKey(Cellline, blank=True, null=True,
229 verbose_name="Background")
230 condition = models.ForeignKey(Condition, blank=True, null=True)
231 antibody = models.ForeignKey(Antibody, blank=True, null=True)
232 affiliations = models.ManyToManyField(
233 Affiliation, related_name='library_affiliations', null=True)
234 tags = models.ManyToManyField(Tag, related_name='library_tags',
235 blank=True, null=True)
236 REPLICATE_NUM = [(x, x) for x in range(1, 7)]
237 replicate = models.PositiveSmallIntegerField(choices=REPLICATE_NUM,
238 blank=True, null=True)
239 experiment_type = models.ForeignKey(ExperimentType)
240 library_type = models.ForeignKey(LibraryType, blank=True, null=True,
241 verbose_name="Adapter Type")
242 multiplex_id = models.CharField(max_length=255,
243 blank=True, null=True,
244 verbose_name="Index ID")
245 creation_date = models.DateField(blank=True, null=True)
246 made_for = models.CharField(max_length=50, blank=True,
247 verbose_name='ChIP/DNA/RNA Made By')
248 made_by = models.CharField(max_length=50, blank=True, default="Lorian")
250 PROTOCOL_END_POINTS = (
252 ('Sample', 'Raw sample'),
253 ('Progress', 'In progress'),
254 ('1A', 'Ligation, then gel'),
255 ('PCR', 'Ligation, then PCR'),
256 ('1Ab', 'Ligation, PCR, then gel'),
257 ('1Ac', 'Ligation, gel, then 12x PCR'),
258 ('1Aa', 'Ligation, gel, then 18x PCR'),
259 ('2A', 'Ligation, PCR, gel, PCR'),
260 ('Done', 'Completed'),
262 PROTOCOL_END_POINTS_DICT = dict(PROTOCOL_END_POINTS)
263 stopping_point = models.CharField(max_length=25,
264 choices=PROTOCOL_END_POINTS,
267 amplified_from_sample = models.ForeignKey(
269 related_name='amplified_into_sample',
270 blank=True, null=True)
272 undiluted_concentration = models.DecimalField(
274 max_digits=5, decimal_places=2, blank=True, null=True,
275 help_text="Undiluted concentration (ng/\u00b5l)")
276 # note \u00b5 is the micro symbol in unicode
277 successful_pM = models.DecimalField(
278 max_digits=9, decimal_places=1, blank=True, null=True)
279 ten_nM_dilution = models.BooleanField(default=False)
280 gel_cut_size = models.IntegerField(default=225, blank=True, null=True)
281 insert_size = models.IntegerField(blank=True, null=True)
282 notes = models.TextField(blank=True)
284 bioanalyzer_summary = models.TextField(blank=True, default="")
285 bioanalyzer_concentration = models.DecimalField(
286 max_digits=5, decimal_places=2, blank=True, null=True,
287 help_text="(ng/\u00b5l)")
288 bioanalyzer_image_url = models.URLField(blank=True, default="")
291 return '#%s: %s' % (self.id, self.library_name)
294 verbose_name_plural = "libraries"
295 # ordering = ["-creation_date"]
298 def antibody_name(self):
299 str = '<a target=_self href="/admin/samples/antibody/' + \
300 self.antibody.id.__str__() + \
301 '/" title="' + self.antibody.__str__() + '">' + \
302 self.antibody.label+'</a>'
304 antibody_name.allow_tags = True
307 return self.library_species.common_name
309 def index_sequences(self):
310 """Return a dictionary of multiplex index id to sequence
311 Return None if the library can't multiplex,
313 if self.library_type is None:
315 if not self.library_type.can_multiplex:
317 if self.multiplex_id is None or len(self.multiplex_id) == 0:
318 return 'Err: id empty'
320 multiplex_expressions = self.multiplex_id.split(',')
321 for multiplex_term in multiplex_expressions:
322 pairs = multiplex_term.split('-')
325 seq = self._lookup_index(pairs[0])
326 elif len(pairs) == 2:
327 key = pairs[0] + '-' + pairs[1]
328 seq0 = self._lookup_index(pairs[0])
329 seq1 = self._lookup_index(pairs[1])
330 if seq0 is None or seq1 is None:
333 seq = seq0 + '-' + seq1
335 raise RuntimeError("Too many - seperated sequences")
337 seq = 'Err: index not found'
341 def _lookup_index(self, multiplex_id):
343 multiplex = MultiplexIndex.objects.get(
344 adapter_type=self.library_type.id,
345 multiplex_id=multiplex_id)
346 return multiplex.sequence
347 except MultiplexIndex.DoesNotExist as e:
350 def index_sequence_text(self, seperator=' '):
351 """Return formatted multiplex index sequences"""
352 sequences = self.index_sequences()
353 if sequences is None:
355 if isinstance(sequences, six.string_types):
357 multiplex_ids = sorted(sequences)
358 return seperator.join(
359 ("%s:%s" % (i, sequences[i]) for i in multiplex_ids))
360 index_sequence_text.short_description = "Index"
362 def affiliation(self):
363 affs = self.affiliations.all().order_by('name')
367 ar.append(t.__str__())
368 return '%s' % (", ".join(ar))
370 def is_archived(self):
371 """returns True if archived else False
373 if self.longtermstorage_set.count() > 0:
378 def lanes_sequenced(self):
379 """Count how many lanes of each type were run.
386 counts = [[0, 0, 0], [0, 0, 0]]
388 for lane in self.lane_set.all():
389 if lane.flowcell.paired_end:
394 if lane.flowcell.read_length < 40:
395 read_type = short_read
396 elif lane.flowcell.read_length < 100:
397 read_type = medium_read
399 read_type = long_read
400 counts[lane_type][read_type] += 1
404 def stopping_point_name(self):
405 end_points = Library.PROTOCOL_END_POINTS_DICT
406 name = end_points.get(self.stopping_point, None)
408 name = "Lookup Error"
409 logger.error("protocol stopping point in database"
410 "didn't match names in library model")
414 affs = self.tags.all().order_by('tag_name')
417 ar.append(t.__str__())
418 return '%s' % (", ".join(ar))
421 str = '<a target=_self href="/admin/experiments/datarun/?q=' + \
423 '" title="Check All Data Runs for This Specific Library ..."' \
426 DataRun.allow_tags = True
428 def aligned_m_reads(self):
429 return getLibReads(self.id)
431 def aligned_reads(self):
432 res = getLibReads(self.id)
436 return '<div style="border:solid red 2px">'+res[2]+'</div>'
438 rc = "%1.2f" % (res[1]/1000000.0)
439 # 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
441 bgcolor = '#ff3300' # Red
442 rc_thr = [10000000, 5000000, 3000000]
443 if self.experiment_type == 'RNA-seq':
444 rc_thr = [20000000, 10000000, 6000000]
446 if res[1] > rc_thr[0]:
447 bgcolor = '#66ff66' # Green
449 if res[1] > rc_thr[1]:
450 bgcolor = '#00ccff' # Blue
452 if res[1] > rc_thr[2]:
453 bgcolor = '#ffcc33' # Orange
454 tstr = '<div style="background-color:'+bgcolor+';color:black">'
455 tstr += res[0].__str__()+' Lanes, '+rc+' M Reads'
458 tstr = 'not processed yet'
460 aligned_reads.allow_tags = True
463 summary_url = self.get_absolute_url()
464 return '<a href="%s">S</a>' % (summary_url,)
465 public.allow_tags = True
467 def get_absolute_url(self):
468 return urlresolvers.reverse('library_to_flowcells',
469 kwargs={'lib_id': str(self.id)})
471 def get_admin_url(self):
472 return urlresolvers.reverse('admin:samples_library_change',
478 Provide some site-specific customization for the django user class
480 # objects = UserManager()
483 ordering = ['first_name', 'last_name', 'username']
486 return '/admin/%s/%s/%d' % (self._meta.app_label,
487 self._meta.module_name, self.id)
490 # return str(self.username) + " (" + str(self.get_full_name()) + u")"
491 return str(self.get_full_name()) + ' (' + str(self.username) + ')'
494 def HTSUserInsertID(sender, instance, **kwargs):
496 Force addition of HTSUsers when someone just modifies the auth_user object
498 u = HTSUser.objects.filter(pk=instance.id)
500 cursor = connection.cursor()
502 'INSERT INTO samples_htsuser (user_ptr_id) VALUES (%s);' %
506 post_save.connect(HTSUserInsertID, sender=User)