models.py 25.3 KB
Newer Older
1
"""
2 3 4 5 6
Copyright 2013-2017 Mathieu Courcelles
CAPA - Center for Advanced Proteomics Analyses and Thibault's lab
IRIC - Universite de Montreal

Data model module for the CLMSVault_app.
7 8
"""

Mathieu Courcelles's avatar
Mathieu Courcelles committed
9 10
# Import standard libraries
import os
11
import re
12 13

# Import Django related libraries
Mathieu Courcelles's avatar
Mathieu Courcelles committed
14 15
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
16
from django.db import models
Mathieu Courcelles's avatar
Mathieu Courcelles committed
17
from django.db.models.signals import post_save, pre_save, pre_delete
18
from django.dispatch import receiver
Mathieu Courcelles's avatar
Mathieu Courcelles committed
19
from django.utils.safestring import mark_safe
20 21

# Import project libraries
Mathieu Courcelles's avatar
Mathieu Courcelles committed
22

23 24 25



26
# # File upload
27
def upload_path_handler(instance, filename):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
28 29 30
    """
    Path handler for dataset file upload.
    """
31
    return "{classname}/{id}-{filename}".format(classname=instance.__class__.__name__,
32
                                                id=instance.pk, filename=filename)
33 34 35



Mathieu Courcelles's avatar
Mathieu Courcelles committed
36 37 38 39 40
class AdminURLMixin(object):
    """
    Generate admin url for objet in this model
    Code from: http://timmyomahony.com/blog/2012/12/12/reversing-admin-urls-and-creating-admin-links-you-models/
    """
41 42


Mathieu Courcelles's avatar
Mathieu Courcelles committed
43 44 45 46 47
    def formated_url(self):
        """
        Returns HTML link for the admin panel.
        """
        return '<a href="%s">%s</a><br />' % (self.get_admin_url(), self)
48 49


Mathieu Courcelles's avatar
Mathieu Courcelles committed
50 51 52 53 54 55 56
    def get_admin_url(self):
        content_type = ContentType \
            .objects \
            .get_for_model(self.__class__)
        return reverse("admin:%s_%s_change" % (
            content_type.app_label,
            content_type.model),
57
                       args=(self.id,))
Mathieu Courcelles's avatar
Mathieu Courcelles committed
58 59


60

Mathieu Courcelles's avatar
Mathieu Courcelles committed
61
#### Model classes #### #### #### #### #### #### #### #### #### #### #### ####
62 63 64 65 66

class CrossLinker(models.Model):
    """
    This class holds the name of all the cross-linkers.
    """
67

68 69
    name = models.CharField(max_length=100, unique=True)

70 71
    linked_formula = models.CharField(max_length=50)

72

73 74 75
    def __unicode__(self):
        return self.name

76

77 78 79 80 81
    class Meta:
        ordering = ['name']



82
class FastaDB(AdminURLMixin, models.Model):
83
    """
84 85
    This class holds the name of all the FASTA database
    and parsing configuration.
86
    """
87

88
    creation_date = models.DateTimeField(auto_now_add=True)
89

90
    name = models.CharField(max_length=200, unique=True)
91

92
    file = models.FileField(upload_to=upload_path_handler,
93
                            max_length=255,
94
                            help_text='Select FASTA file.')
95

96
    identifier_regexp = models.CharField(max_length=50)
97

98
    gene_name_regexp = models.CharField(max_length=50, blank=True)
99

100
    description_regexp = models.CharField(max_length=50, blank=True)
101

102
    species_regexp = models.CharField(max_length=50, blank=True)
103

104
    parsing_status = models.BooleanField(default=False)
105

106
    parsing_log = models.CharField(max_length=1000, blank=True, null=True)
107

108
    sequence_count = models.IntegerField(default=0)
109

110 111
    update = models.BooleanField(help_text=('Trigger update for fields regexp.'
                                            'Will not stay checked after save.'),
112
                                 default=True)
113

114 115 116 117 118

    def refreshParsing(self):
        """
        Update features with new regexp.
        """
119

120 121 122 123 124 125
        if self.pk:
            parsing_log = ''

            for fs in self.fastadb_sequence_set.all():
                parsing_log = fs.extractFeatures(self)
                fs.save()
126

127 128 129 130 131 132
            if parsing_log == '':
                self.parsing_log = 'Ok'
                self.parsing_status = True
            else:
                self.parsing_log = 'Error in: ' + parsing_log,
                self.parsing_status = False
133

134
            self.save()
135

136 137

    def __unicode__(self):
138
        return '[%s] %s' % (self.pk, self.name)
139

140

141
    class Meta:
142 143 144 145 146 147 148 149 150 151 152 153 154
        ordering = ['-creation_date']



class FastaDb_Sequence(models.Model):
    """
    This class holds FASTA database protein sequences
    and extracted features.
    """

    fastadb = models.ForeignKey(FastaDB)

    identifier = models.CharField(max_length=50, blank=True)
155

156
    gene_name = models.CharField(max_length=50, blank=True)
157

158
    raw_description = models.CharField(max_length=250)
159

160
    description = models.CharField(max_length=250, blank=True)
161

162
    species = models.CharField(max_length=100, blank=True)
163

164
    sequence = models.TextField()
165 166


167 168 169 170 171 172
    def extractFeatures(self, fastadb_instance):
        """
        Applies regexp to extract features from description.
        """

        features = ['identifier',
173 174 175
                    'gene_name',
                    'description',
                    'species'
176 177
                    ]

178
        no_match = []
179

180
        for feature in features:
181

182 183 184
            regexp = fastadb_instance.__getattribute__(feature + '_regexp')

            if regexp != '':
185 186 187 188 189 190 191 192 193

                try:
                    matchObj = re.search(regexp,
                                         self.raw_description)
                except re.error:
                    no_match.append('Invalid regular expression for %s.'
                                    % feature)
                    break

194
                if matchObj:
195 196 197 198
                    try:
                        self.__setattr__(feature, matchObj.group(1))
                    except IndexError:
                        no_match.append(feature)
199 200
                else:
                    no_match.append(feature)
201

202
        return ','.join(no_match)
203 204


205 206
    def __unicode__(self):
        return self.identifier
207 208 209 210 211 212 213



class Instrument(models.Model):
    """
    This class holds the name of all the MS instruments.
    """
214

215 216
    name = models.CharField(max_length=100, unique=True)

217

218
    def __unicode__(self):
219
        return self.name
220

221

222 223 224 225
    class Meta:
        ordering = ['name']


226

227
class SearchAlgorithm(models.Model):
228 229 230
    """
    This class holds the name of all the search algorithms.
    """
231

232 233
    name = models.CharField(max_length=100, unique=True)

234

235
    def __unicode__(self):
236
        return self.name
237

238

239 240 241 242
    class Meta:
        ordering = ['name']


243

Mathieu Courcelles's avatar
Mathieu Courcelles committed
244
class Project(AdminURLMixin, models.Model):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
245 246 247
    """
    This class groups datasets.
    """
248

249
    creation_date = models.DateTimeField(auto_now_add=True)
250

Mathieu Courcelles's avatar
Mathieu Courcelles committed
251
    name = models.CharField(max_length=250, unique=True)
252

Mathieu Courcelles's avatar
Mathieu Courcelles committed
253 254 255

    def __unicode__(self):
        return ('[%s] %s') % (self.pk, self.name)
256

257

258 259
    class Meta:
        ordering = ['-creation_date']
Mathieu Courcelles's avatar
Mathieu Courcelles committed
260 261 262



Mathieu Courcelles's avatar
Mathieu Courcelles committed
263
class Dataset(AdminURLMixin, models.Model):
264 265 266
    """
    This class holds dataset information of cross-linked peptides.
    """
267

268
    creation_date = models.DateTimeField(auto_now_add=True)
269

270
    name = models.CharField(max_length=100)
271

Mathieu Courcelles's avatar
Mathieu Courcelles committed
272
    project = models.ForeignKey(Project)
273

274
    cross_linker = models.ForeignKey(CrossLinker, null=True)
275

276
    instrument_name = models.ForeignKey(Instrument, null=True)
277

278
    fasta_db = models.ForeignKey(FastaDB, null=True)
279

280
    search_algorithm = models.ForeignKey(SearchAlgorithm, null=True)
281 282

    description = models.TextField('Detailed description', blank=True)
283 284


285 286 287
    class Meta:
        abstract = False
        ordering = ['-creation_date']
288 289


Mathieu Courcelles's avatar
Mathieu Courcelles committed
290
    def formated_url(self):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
291 292 293
        """
        Returns a formated HTML link for the CLPeptide admin panel. 
        """
294

295
        try:
296 297
            return '<a href="%s">%s</a><br />' % (self.rawdataset.get_admin_url(),
                                                  self)
298
        except Dataset.DoesNotExist:
299 300
            return '<a href="%s">%s</a><br />' % (self.processeddataset.get_admin_url(),
                                                  self)
301 302


Mathieu Courcelles's avatar
Mathieu Courcelles committed
303 304 305 306
    def formated_url_short(self):
        """
        Returns a formated HTML link for the CLPeptide admin panel. 
        """
307

Mathieu Courcelles's avatar
Mathieu Courcelles committed
308
        try:
309 310
            return '<a href="%s">[%s]</a><br />' % (self.rawdataset.get_admin_url(),
                                                    self.pk)
Mathieu Courcelles's avatar
Mathieu Courcelles committed
311
        except Dataset.DoesNotExist:
312 313
            return '<a href="%s">[%s]</a><br />' % (self.processeddataset.get_admin_url(),
                                                    self.pk)
314 315


316
    def __unicode__(self):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
317
        return ('[%s] %s') % (self.pk, self.name)
318

319

320 321 322 323 324 325

class RawDataset(Dataset):
    """
    This class holds raw dataset information of cross-linked peptides
    from search algorithm output.
    """
326

327
    file = models.FileField(upload_to=upload_path_handler,
328
                            max_length=255,
329
                            help_text=('pLink note: Merge the files ending '
330 331 332
                                       'with xlink_qry.proteins.txt from 1.sample\\search'
                                       ' folder.'))

333
    extra_file = models.FileField(upload_to=upload_path_handler, blank=True,
334
                                  max_length=255,
335 336 337 338
                                  help_text=('pLink only: select pLink_combine.spectra.xls '
                                             'from 2.report\\sample1 folder. This file is used '
                                             'to filter FDR filtered hits.'))

339
    parsing_status = models.BooleanField(default=False)
340

341 342 343
    parsing_log = models.CharField(max_length=1000, blank=True, null=True)


344

345 346 347
# Code for dataset file upload
# Code from http://stackoverflow.com/questions/9968532/django-admin-file-upload-with-current-model-id
_UNSAVED_FILEFIELD = 'unsaved_filefield'
348
_UNSAVED_FILEFIELD_EXTRA = 'unsaved_filefield_extra'
349

350 351


352
@receiver(pre_save)
353
def skip_saving_file(sender, instance, **kwargs):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
354 355 356
    """
    Waits that object has been saved before saving files.
    """
357

358
    if sender in [RawDataset, FastaDB, PeakList, Quantification]:
359

360 361 362
        if not instance.pk and not hasattr(instance, _UNSAVED_FILEFIELD):
            setattr(instance, _UNSAVED_FILEFIELD, instance.file)
            instance.file = None
363

364
        if (not instance.pk and not hasattr(instance, _UNSAVED_FILEFIELD_EXTRA)
365
            and hasattr(instance, 'extra_file') and instance.extra_file):
366 367
            setattr(instance, _UNSAVED_FILEFIELD_EXTRA, instance.extra_file)
            instance.extra_file = None
368 369


370

371
@receiver(post_save)
372
def save_file(sender, instance, created, **kwargs):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
373 374 375
    """
    Saves the files now that the dataset object has been saved.
    """
376

377
    if sender in [RawDataset, FastaDB, PeakList, Quantification]:
378

379 380 381 382
        if created and hasattr(instance, _UNSAVED_FILEFIELD):
            instance.file = getattr(instance, _UNSAVED_FILEFIELD)
            if created and hasattr(instance, _UNSAVED_FILEFIELD_EXTRA):
                instance.extra_file = getattr(instance, _UNSAVED_FILEFIELD_EXTRA)
383

384 385
            if instance.file != '':
                instance.save()
386 387 388



389
@receiver(pre_delete, sender=RawDataset)
390
def remove_CLPeptideRD(sender, instance, **kwargs):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
391 392 393
    """
    Remove CLpeptides not linked to any dataset after deletion
    """
394

Mathieu Courcelles's avatar
Mathieu Courcelles committed
395 396 397
    for clpep in instance.clpeptide_set.all():
        if clpep.dataset.count() == 1:
            clpep.delete()
398 399


400

401
class CLPeptide(AdminURLMixin, models.Model):
402 403 404
    """
    This class holds details of cross-linked peptides.
    """
405

406
    dataset = models.ManyToManyField(Dataset)
407

408
    run_name = models.CharField(max_length=1000)
409

Mathieu Courcelles's avatar
Mathieu Courcelles committed
410
    scan_number = models.IntegerField('Scan #')
411

412
    precursor_mz = models.FloatField()
413

Mathieu Courcelles's avatar
Mathieu Courcelles committed
414
    precursor_charge = models.CharField('z', max_length=5)
415

416
    precursor_intensity = models.FloatField()
417

418
    rank = models.IntegerField()
419

420
    match_score = models.FloatField()
421

422
    spectrum_intensity_coverage = models.FloatField()
423

424
    total_fragment_matches = models.IntegerField('# Fragments')
425

426
    delta = models.FloatField()
427

428
    error = models.FloatField()
429

430
    peptide1 = models.CharField(max_length=100)
431

432
    peptide_wo_mod1 = models.CharField(max_length=150)
433

434
    display_protein1 = models.CharField(max_length=250)
435

436
    fs_prot1_id = models.ForeignKey(FastaDb_Sequence,
437
                                    related_name='clpep1', null=True)
438

Mathieu Courcelles's avatar
Mathieu Courcelles committed
439
    peptide_position1 = models.IntegerField('Pep. Pos. 1')
440

441
    pep1_link_pos = models.IntegerField()
442

443
    peptide2 = models.CharField(max_length=100)
444

445
    peptide_wo_mod2 = models.CharField(max_length=150)
446

447
    display_protein2 = models.CharField(max_length=250)
448

449 450
    fs_prot2_id = models.ForeignKey(FastaDb_Sequence,
                                    related_name='clpep2', null=True)
451

Mathieu Courcelles's avatar
Mathieu Courcelles committed
452
    peptide_position2 = models.IntegerField('Pep. Pos. 2')
453

454
    pep2_link_pos = models.IntegerField()
455

456
    autovalidated = models.BooleanField(default=False)
457

458
    validated = models.CharField(max_length=50)
459

460
    rejected = models.BooleanField(default=False)
461

462
    notes = models.CharField(max_length=100)
463

464
    LINK_TYPE_CHOICES = (
465 466 467 468 469 470 471
        ('Inter-protein', 'Inter-protein'),
        ('Intra-protein', 'Intra-protein'),
        ('Intra-peptide', 'Intra-peptide'),
        ('Dead-end', 'Dead-end'),
        ('Linear peptide', 'Linear peptide'),
    )

472
    link_type = models.CharField(max_length=50, choices=LINK_TYPE_CHOICES)
473

474
    cross_link = models.BooleanField('Inter-peptide cross-link', default=False)
475

476
    not_decoy = models.BooleanField(default=False)
Mathieu Courcelles's avatar
Mathieu Courcelles committed
477 478 479

    scan_file_index = models.BigIntegerField(default=-1)

480
    retention_time = models.FloatField(default=0)
481 482 483 484 485 486 487 488 489 490 491 492 493

    # Fields for XlinkX
    n_score_a = models.FloatField(blank=True, null=True)

    n_score_a_MS2_MS3 = models.FloatField(blank=True, null=True)

    n_score_b = models.FloatField(blank=True, null=True)

    n_score_b_MS2_MS3 = models.FloatField(blank=True, null=True)

    spectra_num = models.IntegerField(blank=True, null=True)


Mathieu Courcelles's avatar
Mathieu Courcelles committed
494 495
    class Meta:
        ordering = ['-match_score']
496 497


498 499 500 501 502
    def formated_url_jsmol(self):
        """
        Returns HTML link for the admin panel.
        """
        return '<a href="%s" style="text-decoration:underline;">%s</a><br />' % (self.get_admin_url(), self)
503

Mathieu Courcelles's avatar
Mathieu Courcelles committed
504 505 506 507 508 509 510 511 512 513 514

    def msms(self):

        if self.scan_file_index == -1:
            return ''

        url = reverse('view_msms', args=[self.pk])

        return mark_safe('<a href="%s" target="_blank"><i class="fa fa-bar-chart "></i></a>' % url)


Mathieu Courcelles's avatar
Mathieu Courcelles committed
515
    def fixAutovalidated(self):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
516 517 518
        """
        Convert the string value stored in the field to a boolean type.
        """
519

Mathieu Courcelles's avatar
Mathieu Courcelles committed
520
        if str(self.autovalidated).lower() == 'true':
Mathieu Courcelles's avatar
Mathieu Courcelles committed
521 522 523
            self.autovalidated = True
        else:
            self.autovalidated = False
524

525

526 527 528 529 530
    def guessLinkType(self):
        """
        This method guess the type of peptide cross-link since Xi is not
        providing this information.
        """
531

532 533
        self.link_type = 'Inter-protein'
        self.cross_link = True
534

535
        if self.peptide_wo_mod1 == self.peptide_wo_mod2:
536
            self.link_type = 'Inter-protein'
537
            self.cross_link = True
538

539
        elif self.display_protein1 == self.display_protein2:
540
            self.link_type = 'Intra-protein'
541
            self.cross_link = True
542

543
        else:
544

545
            if self.pep2_link_pos == -1:
546 547
                self.link_type = 'Dead-end'
                self.cross_link = False
548

549 550
                # Xi format for dead-end and linear peptides
                if self.pep1_link_pos == -1:
551

552
                    self.link_type = 'Linear peptide'
553

554
                    deadend_keywords = ['bs3', 'bs2g', 'adh_oh']
555

556 557 558 559 560 561
                    pepseq = self.peptide1
                    pepseq = re.sub('Cm', 'C', pepseq)
                    pepseq = re.sub('Mox', 'M', pepseq)
                    pepseq = re.sub('Sp', 'S', pepseq)
                    pepseq = re.sub('Tp', 'T', pepseq)
                    pepseq = re.sub('Yp', 'Y', pepseq)
562

563 564
                    for key in deadend_keywords:
                        pos = pepseq.find(key)
565

566 567 568 569
                        if pos != -1:
                            self.link_type = 'Dead-end'
                            self.pep1_link_pos = pos + 1
                            break
570

571
            elif self.pep2_link_pos > -1 and self.peptide_wo_mod2 == '-':
572
                self.link_type = 'Intra-peptide'
573
                self.cross_link = True
574 575


Mathieu Courcelles's avatar
Mathieu Courcelles committed
576
    def isDecoy(self):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
577 578 579
        """
        Convert the string value stored in the field to a boolean type.
        """
580

581
        if (self.display_protein1 == 'DECOY' or
582 583
                    self.display_protein2 == 'DECOY'):

Mathieu Courcelles's avatar
Mathieu Courcelles committed
584
            self.not_decoy = False
585

Mathieu Courcelles's avatar
Mathieu Courcelles committed
586 587
        else:
            self.not_decoy = True
588 589


590 591
    def __unicode__(self):
        return '%s' % self.pk
592 593


Mathieu Courcelles's avatar
Mathieu Courcelles committed
594 595 596 597
    def uniqueKey(self, params):
        """
        Returns a unique key that describe the cross-linked peptide.
        """
598

Mathieu Courcelles's avatar
Mathieu Courcelles committed
599
        key = list()
600
        separator = '~~'
601

Mathieu Courcelles's avatar
Mathieu Courcelles committed
602 603 604
        if params.has_key('sequence'):
            seq_list = [self.peptide_wo_mod1, self.peptide_wo_mod2]
            seq_list.sort()
Mathieu Courcelles's avatar
Mathieu Courcelles committed
605
            key.extend(seq_list)
606

Mathieu Courcelles's avatar
Mathieu Courcelles committed
607 608 609
        if params.has_key('sequenceMods'):
            seq_list = [self.peptide1, self.peptide2]
            seq_list.sort()
Mathieu Courcelles's avatar
Mathieu Courcelles committed
610
            key.extend(seq_list)
611

Mathieu Courcelles's avatar
Mathieu Courcelles committed
612
        if params.has_key('sequenceCpos'):
613
            seq_list = [separator.join([self.peptide_wo_mod1,
614
                                        str(self.pep1_link_pos)]),
615
                        separator.join([self.peptide_wo_mod2,
616
                                        str(self.pep2_link_pos)])
Mathieu Courcelles's avatar
Mathieu Courcelles committed
617
                        ]
Mathieu Courcelles's avatar
Mathieu Courcelles committed
618
            seq_list.sort()
Mathieu Courcelles's avatar
Mathieu Courcelles committed
619
            key.extend(seq_list)
620

Mathieu Courcelles's avatar
Mathieu Courcelles committed
621
        if params.has_key('proteinPpos'):
622

623 624 625 626
            identifier1 = ''

            try:
                identifier1 = self.fs_prot1_id.identifier
627

628 629 630
            except:
                if self.peptide1:
                    identifier1 = 'DECOY'
631

632
            identifier2 = ''
633

634 635
            try:
                identifier2 = self.fs_prot2_id.identifier
636

637 638 639
            except:
                if self.peptide2:
                    identifier2 = 'DECOY'
640

641
            seq_list = [separator.join([identifier1,
642
                                        str(self.peptide_position1 +
643 644 645
                                            self.pep1_link_pos - 1)
                                        ]),
                        separator.join([identifier2,
646
                                        str(self.peptide_position2 +
647 648
                                            self.pep2_link_pos - 1)
                                        ])
Mathieu Courcelles's avatar
Mathieu Courcelles committed
649
                        ]
Mathieu Courcelles's avatar
Mathieu Courcelles committed
650
            seq_list.sort()
Mathieu Courcelles's avatar
Mathieu Courcelles committed
651
            key.extend(seq_list)
652

Mathieu Courcelles's avatar
Mathieu Courcelles committed
653
        if params.has_key('charge'):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
654
            key.extend(self.precursor_charge)
Mathieu Courcelles's avatar
Mathieu Courcelles committed
655

656
        return separator.join(key)
657 658 659



660 661 662 663
class Quantification(models.Model):
    """
    This class Quantification experiment.
    """
664

665
    creation_date = models.DateTimeField(auto_now_add=True)
666

667
    name = models.CharField(max_length=100)
668

669
    TYPE_CHOICES = (
670 671 672 673 674
        ('FC', 'Fold change'),
        ('FClog2', 'Fold change log2'),
        ('FClog10', 'Fold change log10'),
    )

675
    quantification_type = models.CharField(max_length=20, choices=TYPE_CHOICES)
676

677
    parsing_status = models.BooleanField(default=False)
678

679
    parsing_log = models.CharField(max_length=1000, blank=True, null=True)
680 681 682 683 684 685 686 687

    HEADER_CHOICES = (
        ('CF', '"CLPeptideId","FoldChange"'),
        ('FSF', '"File","ScanNumber","FoldChange"')
    )

    file_header = models.CharField(max_length=20, choices=HEADER_CHOICES, null=True)

688 689 690
    file = models.FileField(upload_to=upload_path_handler,
                            max_length=255,
                            help_text='Select quantification file.')
691 692


693 694
    def __unicode__(self):
        return '%s - %s' % (self.pk, self.name)
695 696


697 698 699

class QuantificationFC(models.Model):
    quantification = models.ForeignKey(Quantification)
700

701
    clpeptide = models.ForeignKey(CLPeptide)
702 703 704

    fold_change = models.FloatField()

705 706


Mathieu Courcelles's avatar
Mathieu Courcelles committed
707
class CLPeptideFilter(AdminURLMixin, models.Model):
Mathieu Courcelles's avatar
.  
Mathieu Courcelles committed
708 709 710
    """
    This class holds pre-defined filters for cross-linked peptides.
    """
711

Mathieu Courcelles's avatar
.  
Mathieu Courcelles committed
712
    creation_date = models.DateTimeField(auto_now_add=True)
713

Mathieu Courcelles's avatar
Mathieu Courcelles committed
714
    name = models.CharField('Filter name', max_length=100)
715

Mathieu Courcelles's avatar
.  
Mathieu Courcelles committed
716
    description = models.TextField('Detailed description', blank=True)
717

718 719
    fp_cutoff = models.FloatField('False positive cutoff',
                                  help_text=('Range from 0 to 1. '
720
                                             'Applied before unique filter.'),
721
                                  blank=True, null=True)
722

723
    remove_decoy = models.BooleanField('Remove decoy hits', default=False)
724

725
    KK_link = models.BooleanField('Remove non K-K cross-links', default=False)
726

Mathieu Courcelles's avatar
Mathieu Courcelles committed
727
    UIN_CHOICES = (
728 729 730 731 732
        ('dataset', 'Dataset'),
        ('msrun', 'MS run'),
    )

    unique_in = models.CharField('Unique/False positive peptide in',
733 734 735
                                 max_length=20,
                                 choices=UIN_CHOICES,
                                 blank=True, null=True)
736

Mathieu Courcelles's avatar
Mathieu Courcelles committed
737
    UKEY_CHOICES = (
738 739 740 741 742 743 744
        ('sequence', 'Peptide sequences'),
        ('sequence-charge', 'Peptide sequences & charge'),
        ('sequenceMods', 'Peptide sequences & mods'),
        ('sequenceCpos', 'Peptide sequences & cross-link positions'),
        ('proteinPpos', 'Proteins & cross-link positions'),
    )

745 746
    unique_key = models.CharField('Unique peptide key', max_length=20,
                                  choices=UKEY_CHOICES,
Mathieu Courcelles's avatar
Mathieu Courcelles committed
747
                                  blank=True, null=True)
748

749
    OPERATOR_CHOICES = (
750 751 752 753 754 755
        ('gt', 'Greater than'),
        ('gte', 'Greater than or equal to'),
        ('lt', 'Less than'),
        ('lte', 'Less than or equal to'),
    )

756
    LOGIC_CHOICES = (
757 758 759 760 761
        ('|', 'OR'),
        ('&', 'AND'),

    )

762 763
    quantification_experiment = models.ForeignKey(Quantification, null=True,
                                                  blank=True)
764

765
    quantification_value_1 = models.FloatField(null=True, blank=True)
766 767 768 769 770 771 772 773 774

    quantification_operator_1 = models.CharField(max_length=3,
                                                 choices=OPERATOR_CHOICES,
                                                 null=True, blank=True)

    quantification_logic = models.CharField(max_length=1,
                                            choices=LOGIC_CHOICES,
                                            null=True, blank=True)

775
    quantification_value_2 = models.FloatField(null=True, blank=True)
776

777
    quantification_operator_2 = models.CharField(max_length=3,
778 779 780 781
                                                 choices=OPERATOR_CHOICES,
                                                 null=True, blank=True)


782 783
    def __unicode__(self):
        return ('[%s] %s') % (self.pk, self.name)
784 785


Mathieu Courcelles's avatar
.  
Mathieu Courcelles committed
786 787

class CLPeptideFilterParam(models.Model):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
788 789 790
    """
    Stores unit filter details (method, field, lookup and value).
    """
791

Mathieu Courcelles's avatar
.  
Mathieu Courcelles committed
792
    clpeptidefilter = models.ForeignKey(CLPeptideFilter)
793

Mathieu Courcelles's avatar
.  
Mathieu Courcelles committed
794
    METHOD_CHOICES = (
795 796 797 798
        ('exclude', 'Exclude'),
        ('filter', 'Filter'),
    )

799
    method = models.CharField(max_length=20, choices=METHOD_CHOICES)
800

801
    field = models.CharField(max_length=100, choices=[(field.name, field.name) \
802 803
                                                      for field in CLPeptide._meta.fields])

Mathieu Courcelles's avatar
.  
Mathieu Courcelles committed
804
    LOOKUP_CHOICES = (
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
        ('exact', 'Exact match'),
        ('iexact', 'Exact match case insentive'),
        ('contains', 'Contains'),
        ('icontains', 'Contains case insentive'),
        ('gt', 'Greater than'),
        ('gte', 'Greater than or equal to'),
        ('lt', 'Less than'),
        ('lte', 'Less than or equal to'),
        ('startswith', 'Starts-with'),
        ('istartswith', 'Starts-with case insensitive'),
        ('endswith', 'Ends-with'),
        ('iendswith', 'Ends-with case insensitive'),
        ('isnull', 'Is null'),
        ('regex', 'Regular expression match'),
        ('iregex', 'Regular expression match case insensitive'),
    )

Mathieu Courcelles's avatar
.  
Mathieu Courcelles committed
822
    field_lookup = models.CharField(max_length=50, choices=LOOKUP_CHOICES)
823

824
    value = models.CharField(max_length=50)
825 826


827 828 829 830 831 832

class ProcessedDataset(Dataset):
    """
    This class holds dataset information of cross-linked peptides
    after grouping and/or filtering.
    """
833 834

    datasets = models.ManyToManyField(Dataset,
835
                                      related_name='processed_datasets')
836 837

    clpeptidefilter = models.ForeignKey(CLPeptideFilter, blank=True,
838
                                        null=True)
839 840


841

842
@receiver(pre_delete, sender=ProcessedDataset)
843
def remove_CLPeptidePD(sender, instance, **kwargs):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
844 845 846
    """
    Removes CLpeptides not linked to any dataset after deletion
    """
847

848 849
    for clpep in instance.clpeptide_set.all():
        if clpep.dataset.count() == 1:
Mathieu Courcelles's avatar
Mathieu Courcelles committed
850
            clpep.delete()
851 852 853 854 855 856 857 858



def upload_path_handler_PDB(instance, filename):
    """
    Path handler for dataset file upload.
    """
    return "{classname}/{filename}.pdb".format(classname=instance.__class__.__name__,
859 860
                                               filename=instance.identifier)

861 862 863 864 865 866


class PDB(models.Model):
    """
    This class holds PDB stored on the server.
    """
867 868 869

    identifier = models.CharField(max_length=50, unique=True, )

870
    title = models.CharField(max_length=1000)
871

872
    file = models.FileField(upload_to=upload_path_handler_PDB,
873
                            max_length=255,
874
                            help_text='Select PDB file.')
875 876


877 878
    def __unicode__(self):
        return '%s - %s' % (self.identifier, self.title)
879 880


881
    class Meta:
882
        ordering = ['identifier']
883 884


885
def upload_path_handler_peak_list(instance, file_name):
Mathieu Courcelles's avatar
Mathieu Courcelles committed
886
    """