models.py 58.3 KB
Newer Older
1
2
3
4
"""
Models used in iPPI-DB
"""

Hervé  MENAGER's avatar
Hervé MENAGER committed
5
from __future__ import unicode_literals
6

7
import operator
8
import re
9
import sys
Hervé  MENAGER's avatar
Hervé MENAGER committed
10

11
from django.conf import settings
12
from django.contrib.auth import get_user_model
13
from django.core.exceptions import ValidationError
14
from django.db import models, transaction
15
from django.db.models import FloatField, IntegerField, BooleanField
16
from django.db.models import Max, Count, F, Q, Case, When, Subquery, OuterRef
17
from django.db.models.functions import Cast
18
from django.urls import reverse
19
from django.utils.translation import ugettext_lazy as _
20
21
22
from django_celery_results.models import TaskResult
from django.dispatch import receiver
from django.db.models.signals import post_save
Hervé  MENAGER's avatar
Hervé MENAGER committed
23

24
from .utils import FingerPrinter, smi2inchi, smi2inchikey
25
26
27
28
29
30
31
32
33
from .ws import (
    get_pubmed_info,
    get_google_patent_info,
    get_uniprot_info,
    get_taxonomy_info,
    get_go_info,
    get_pfam_info,
    get_doi_info,
)
34

Hervé  MENAGER's avatar
Hervé MENAGER committed
35

36
37
38
39
class AutoFillableModel(models.Model):
    """
    AutoFillableModel makes it possible to automatically fill model fields from
    external sources in the autofill() method
40
41
    The save method allows to either include autofill or not. in autofill kwarg
    is set to True, save() will first call autofill(), otherwise it won't
42
43
44
45
46
47
    """

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
48
        auto_fill_needed = not self.is_autofill_done()
49
        if kwargs.get("autofill") is True or auto_fill_needed:
50
            auto_fill_needed = True
51
            self.autofill()
52
53
        if "autofill" in kwargs:
            del kwargs["autofill"]
Hervé  MENAGER's avatar
Hervé MENAGER committed
54
        super(AutoFillableModel, self).save(*args, **kwargs)
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
55
        if auto_fill_needed:
56
            self.autofill_post_save()
57

58
59
60
    def autofill(self):
        raise NotImplementedError()

61
62
    def autofill_post_save(self):
        """
63
64
        method called after the save is done, usefull for setting m2m
        relations
65
66
67
68
        :return:
        """
        pass

69
70
71
    def is_autofill_done(self):
        return True

72
73

class Bibliography(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
74
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
75
76
    Bibliography references
    (publications or patents)
Hervé  MENAGER's avatar
Hervé MENAGER committed
77
    """
78
79

    SOURCES = (("PM", "PubMed ID"), ("PT", "Patent"), ("DO", "DOI"))
80
    id_source_validators = dict(
81
82
83
        PM=re.compile(r"^[0-9]+$"),
        PT=re.compile(r"^.*$"),
        DO=re.compile(r"^10.\d{4,9}/.+$"),
84
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
85
    source = models.CharField(
86
87
        "Bibliographic type", max_length=2, choices=SOURCES, default=SOURCES[0][0]
    )
88
    id_source = models.TextField("Bibliographic ID")
89
90
91
92
93
94
95
96
97
98
99
    title = models.TextField("Title")
    journal_name = models.TextField("Journal name", null=True, blank=True)
    authors_list = models.TextField("Authors list")
    biblio_year = models.PositiveSmallIntegerField("Year")
    cytotox = models.BooleanField("Cytotoxicity data", default=False)
    in_silico = models.BooleanField("in silico study", default=False)
    in_vitro = models.BooleanField("in vitro study", default=False)
    in_vivo = models.BooleanField("in vivo study", default=False)
    in_cellulo = models.BooleanField("in cellulo study", default=False)
    pharmacokinetic = models.BooleanField("pharmacokinetic study", default=False)
    xray = models.BooleanField("X-Ray data", default=False)
Hervé  MENAGER's avatar
Hervé MENAGER committed
100

101
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
102
103
104
105
        """
        fetch information from external services
        (Pubmed or Google patents)
        """
106
        if self.source == "PM":
107
            info = get_pubmed_info(self.id_source)
108
        elif self.source == "PT":
109
            info = get_google_patent_info(self.id_source)
110
        elif self.source == "DO":
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
111
112
113
            info = get_doi_info(self.id_source)
        else:
            raise NotImplementedError()
114
115
116
117
        self.title = info["title"]
        self.journal_name = info["journal_name"]
        self.authors_list = info["authors_list"]
        self.biblio_year = info["biblio_year"]
Hervé  MENAGER's avatar
Hervé MENAGER committed
118

119
120
121
    def is_autofill_done(self):
        return len(self.title) > 0

122
123
    def clean(self):
        super().clean()
124
125
126
        Bibliography.validate_source_id(self.id_source, self.source)

    def has_external_url(self):
127
        return self.source == "PM" or self.source == "DO"
128
129

    def get_external_url(self):
130
        if self.source == "PM":
131
            return "https://www.ncbi.nlm.nih.gov/pubmed/" + str(self.id_source)
132
        if self.source == "DO":
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
133
            return "https://doi.org/" + str(self.id_source)
134
135
136
137
138

    @staticmethod
    def validate_source_id(id_source, source):
        id_source_validator = Bibliography.id_source_validators[source]
        if not id_source_validator.match(id_source):
139
140
            raise ValidationError(
                dict(
141
142
                    id_source=_(
                        f"Must match pattern {id_source_validator.pattern}"
143
144
                        " for this selected source"
                    )
145
146
                )
            )
147
        return True
148

Hervé  MENAGER's avatar
Hervé MENAGER committed
149
    class Meta:
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
        verbose_name_plural = "Bibliographies"
        verbose_name = "Bibliography"

    def data_and_study(self):
        ret = []
        for f in [
            "cytotox",
            "xray",
            "in_silico",
            "in_vitro",
            "in_cellulo",
            "in_vivo",
            "pharmacokinetic",
        ]:
            if getattr(self, f, False):
                ret.append(self._meta.get_field(f).verbose_name.title())
        return ", ".join(ret)
Hervé  MENAGER's avatar
Hervé MENAGER committed
167

168
    def __str__(self):
169
        return "{}, {}".format(self.source, self.id_source)
170

171
    def get_absolute_url(self):
172
        return reverse("biblio-view", kwargs={"biblio_pk": self.pk})
173

Hervé  MENAGER's avatar
Hervé MENAGER committed
174

175
class Taxonomy(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
176
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
177
    Taxonomy IDs (from NCBI Taxonomy)
Hervé  MENAGER's avatar
Hervé MENAGER committed
178
179
    and the corresponding human-readable name
    """
180

Hervé  MENAGER's avatar
Hervé MENAGER committed
181
    taxonomy_id = models.DecimalField(
182
183
184
        "NCBI TaxID", unique=True, max_digits=9, decimal_places=0
    )
    name = models.CharField("Organism name", max_length=200)
185

186
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
187
188
189
190
        """
        fetch information from external services
        (NCBI Entrez)
        """
191
        info = get_taxonomy_info(self.taxonomy_id)
192
        self.name = info["scientific_name"]
193

194
195
196
    def __str__(self):
        return self.name

Hervé  MENAGER's avatar
Hervé MENAGER committed
197
198
    class Meta:
        verbose_name_plural = "taxonomies"
Hervé  MENAGER's avatar
Hervé MENAGER committed
199

Hervé  MENAGER's avatar
Hervé MENAGER committed
200

201
class MolecularFunction(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
202
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
203
    Molecular functions (from Gene Ontology)
Hervé  MENAGER's avatar
Hervé MENAGER committed
204
205
    and the corresponding human-readable description
    """
206
207

    go_id = models.CharField("Gene Ontology ID", unique=True, max_length=10)
Hervé  MENAGER's avatar
Hervé MENAGER committed
208
    # GO term id format: 'GO:0000000'
209
    description = models.CharField("description", max_length=500)
Hervé  MENAGER's avatar
Hervé MENAGER committed
210

211
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
212
213
214
215
        """
        fetch information from external services
        (EBI OLS)
        """
216
        info = get_go_info(self.go_id)
217
        self.description = info["label"]
218

219
220
221
    def is_autofill_done(self):
        return self.description is not None and len(self.description) > 0

222
223
    @property
    def name(self):
224
        return self.go_id + " " + self.description
225

226
227
228
    def __str__(self):
        return self.description

Hervé  MENAGER's avatar
Hervé MENAGER committed
229

230
class Protein(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
231
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
232
    Protein information (from Uniprot)
Hervé  MENAGER's avatar
Hervé MENAGER committed
233
234
    and the corresponding human-readable name
    """
235
236

    uniprot_id = models.CharField("Uniprot ID", unique=True, max_length=10)
Hervé  MENAGER's avatar
Hervé MENAGER committed
237
    recommended_name_long = models.CharField(
238
        "Uniprot Recommended Name (long)", max_length=75, blank=True, null=True
239
240
    )
    short_name = models.CharField("Short name", max_length=50)
241
    gene_name = models.CharField("Gene name", max_length=30, blank=True, null=True)
242
243
    entry_name = models.CharField("Entry name", max_length=30)
    organism = models.ForeignKey("Taxonomy", models.CASCADE)
Hervé  MENAGER's avatar
Hervé MENAGER committed
244
    molecular_functions = models.ManyToManyField(MolecularFunction)
245
    domains = models.ManyToManyField("Domain")
Hervé  MENAGER's avatar
Hervé MENAGER committed
246

247
    @transaction.atomic
248
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
249
250
251
252
        """
        fetch information from external services
        (Uniprot) and create Taxonomy/Molecular Functions if needed
        """
253
        info = get_uniprot_info(self.uniprot_id)
254
        self.recommended_name_long = info["recommended_name"]
255

256
        gene_names = info["gene_names"]
257
        # put whatever name it find
258
259
260
261
        try:
            self.gene_name = gene_names[0]["name"]
        except IndexError:
            pass
262
263
264
265
266
267
        # then try to find the primary, if present
        for gene_name in gene_names:
            if gene_name["type"] == "primary":
                self.gene_name = gene_name["name"]
                break

268
269
        self.entry_name = info["entry_name"]
        self.short_name = info["short_name"]
270
        try:
271
            taxonomy = Taxonomy.objects.get(taxonomy_id=info["organism"])
272
273
        except Taxonomy.DoesNotExist:
            taxonomy = Taxonomy()
274
            taxonomy.taxonomy_id = info["organism"]
275
            taxonomy.save(autofill=True)
276
        self.organism = taxonomy
277
        self.__info = info
278

279
280
    def autofill_post_save(self):
        info = self.__info
281
282
        for go_id in info["molecular_functions"]:
            mol_function, created = MolecularFunction.objects.get_or_create(go_id=go_id)
283
            self.molecular_functions.add(mol_function)
284

285
        for domain_id in info["domains"]:
286
            domain, created = Domain.objects.get_or_create(pfam_acc=domain_id)
287
288
            self.domains.add(domain)

289
    def is_autofill_done(self):
290
        return len(self.entry_name) > 0
291

292
    def __str__(self):
293
294
295
        return "{} ({})".format(
            self.uniprot_id, self.recommended_name_long or self.short_name
        )
296

Hervé  MENAGER's avatar
Hervé MENAGER committed
297

298
class Domain(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
299
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
300
    Domain (i.e. Protein domain) information (from PFAM)
Hervé  MENAGER's avatar
Hervé MENAGER committed
301
    """
302
303
304
305
306
307
308

    pfam_acc = models.CharField("Pfam Accession", max_length=10, unique=True)
    pfam_id = models.CharField("Pfam Family Identifier", max_length=20)
    pfam_description = models.CharField("Pfam Description", max_length=100)
    domain_family = models.CharField(
        "Domain family", max_length=25, blank=True, default=""
    )
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
309

Hervé  MENAGER's avatar
Hervé MENAGER committed
310
311
    # TODO: what is this field? check database
    # contents
312

313
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
314
315
316
317
        """
        fetch information from external services
        (PFAM)
        """
318
        info = get_pfam_info(self.pfam_acc)
319
320
        self.pfam_id = info["id"]
        self.pfam_description = info["description"]
Hervé  MENAGER's avatar
Hervé MENAGER committed
321

322
323
324
    def is_autofill_done(self):
        return self.pfam_id is not None and len(self.pfam_id) > 0

325
326
327
328
    @property
    def name(self):
        return self.pfam_id

329
    def __str__(self):
330
        return f"{self.pfam_acc} ({self.pfam_id}-{self.pfam_description})"
331

Hervé  MENAGER's avatar
Hervé MENAGER committed
332

333
class ProteinDomainComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
334
    """
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
335
    Protein-Domain association
Hervé  MENAGER's avatar
Hervé MENAGER committed
336
    """
337
338
339
340

    protein = models.ForeignKey("Protein", models.CASCADE)
    domain = models.ForeignKey("Domain", models.CASCADE, null=True, blank=True)
    ppc_copy_nb = models.IntegerField("Number of copies of the protein in the complex")
Hervé  MENAGER's avatar
Hervé MENAGER committed
341

Hervé  MENAGER's avatar
Hervé MENAGER committed
342
343
    class Meta:
        verbose_name_plural = "complexes"
344

345
    def __str__(self):
346
347
348
        return "{} {}-{}".format(
            self.protein.short_name, self.protein_id, self.domain_id
        )
349

350
351
    def name(self):
        return self.protein.short_name
Hervé  MENAGER's avatar
Hervé MENAGER committed
352

353

354
class ProteinDomainBoundComplex(ProteinDomainComplex):
Hervé  MENAGER's avatar
Hervé MENAGER committed
355
356
357
    """
    Protein-Domain association with a "bound complex" role
    """
358
359

    ppp_copy_nb_per_p = models.IntegerField(_("ppp_copy_nb_per_p"))
Hervé  MENAGER's avatar
Hervé MENAGER committed
360

361
362
    class Meta:
        verbose_name_plural = "bound complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
363
364


365
class ProteinDomainPartnerComplex(ProteinDomainComplex):
Hervé  MENAGER's avatar
Hervé MENAGER committed
366
367
368
    """
    Protein-Domain association with a "partner complex" role
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
369

370
371
    class Meta:
        verbose_name_plural = "partner complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
372

Hervé  MENAGER's avatar
Hervé MENAGER committed
373

374
class Symmetry(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
375
376
377
    """
    Symmetry of a PPI
    """
378
379
380

    code = models.CharField("Symmetry code", max_length=2)
    description = models.CharField("Description", max_length=300)
Hervé  MENAGER's avatar
Hervé MENAGER committed
381

382
383
384
    class Meta:
        verbose_name_plural = "symmetries"

385
    def __str__(self):
386
        return "{} ({})".format(self.code, self.description)
387

388
389

class Disease(models.Model):
390
391
    name = models.CharField("Disease", max_length=256)
    identifier = models.CharField("Identifier", max_length=32, null=True, blank=True)
392
393

    def __str__(self):
394
        return "%s (%s)" % (self.name, self.identifier)
395

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
396

Hervé  MENAGER's avatar
Hervé MENAGER committed
397
class PpiFamily(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
398
399
400
    """
    PPI Family
    """
401
402

    name = models.CharField("Name", max_length=30, unique=True)
Hervé  MENAGER's avatar
Hervé MENAGER committed
403

404
405
406
    class Meta:
        verbose_name_plural = "PPI Families"

Hervé  MENAGER's avatar
Hervé MENAGER committed
407
408
    def __str__(self):
        return self.name
Hervé  MENAGER's avatar
Hervé MENAGER committed
409

Hervé  MENAGER's avatar
Hervé MENAGER committed
410

411
class Ppi(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
412
413
414
    """
    PPI
    """
415
416

    pdb_id = models.CharField("PDB ID", max_length=4, null=True, blank=True)
Hervé  MENAGER's avatar
Hervé MENAGER committed
417
    pockets_nb = models.IntegerField(
418
419
        "Total number of pockets in the complex", default=1
    )
420
    symmetry = models.ForeignKey(Symmetry, models.CASCADE)
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
421
    diseases = models.ManyToManyField(Disease, blank=True)
422
423
    family = models.ForeignKey(PpiFamily, models.CASCADE, null=True, blank=True)
    name = models.TextField("PPI name", null=True, blank=True)
Hervé  MENAGER's avatar
Hervé MENAGER committed
424

425
    def __str__(self):
426
        return "PPI #{} on {}".format(self.id, self.name)
427

428
    def get_absolute_url(self):
429
        return reverse("ppi-view", kwargs={"ppi_pk": self.pk})
430

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
431
    def is_autofill_done(self):
432
        return self.name != ""
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
433

434
435
436
    def autofill(self):
        # name is denormalized and stored in the database to reduce SQL queries in query mode
        self.name = self.compute_name_from_protein_names()
437
438
439
440
441

    def get_ppi_bound_complexes(self):
        """
        return bound ppi complexes belonging to this ppi
        """
442
        return PpiComplex.objects.filter(
443
444
            ppi=self, complex__in=ProteinDomainBoundComplex.objects.all()
        )
445

446
    def compute_name_from_protein_names(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
447
        all_protein_names = set(
448
449
            [
                ppi_complex.complex.protein.short_name
450
451
452
                for ppi_complex in self.ppicomplex_set.all()
            ]
        )
Hervé  MENAGER's avatar
Hervé MENAGER committed
453
        bound_protein_names = set(
454
455
            [
                ppi_complex.complex.protein.short_name
456
457
458
                for ppi_complex in self.get_ppi_bound_complexes()
            ]
        )
459
        partner_protein_names = all_protein_names - bound_protein_names
460
461
        bound_str = ",".join(sorted(bound_protein_names))
        partner_str = ",".join(sorted(partner_protein_names))
462
        name = bound_str
463
464
        if partner_str != "":
            name += " / " + partner_str
465
        return name
466

Hervé  MENAGER's avatar
Hervé MENAGER committed
467

Hervé  MENAGER's avatar
Hervé MENAGER committed
468
class PpiComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
469
470
471
    """
    PPI Complex
    """
472

473
474
    ppi = models.ForeignKey(Ppi, models.CASCADE)
    complex = models.ForeignKey(ProteinDomainComplex, models.CASCADE)
475
    cc_nb = models.IntegerField(verbose_name=_("cc_nb_verbose_name"), default=1)
Hervé  MENAGER's avatar
Hervé MENAGER committed
476
477
478
479

    class Meta:
        verbose_name_plural = "Ppi complexes"

480
    def __str__(self):
481
482
        return "PPI {}, Complex {} ({})".format(self.ppi, self.complex, self.cc_nb)

483

484
485
class CompoundsManager(models.Manager):
    def for_user(self, current_user):
486
487
488
489
490
        """
        Get compounds visible to a given user
        i.e. validated or created by the user or
        all of them if the user is an admin
        """
491
492
493
494
495
496
497
498
499
        qs = self.get_queryset()
        if current_user.is_anonymous:
            qs = qs.exclude(compoundaction__ppi__contribution__validated=False)
        elif not current_user.is_superuser:
            qs = qs.exclude(
                Q(compoundaction__ppi__contribution__validated=False),
                ~Q(compoundaction__ppi__contribution__contributor=current_user),
            )
        return qs
500

501
502
503
504
505
506
507
508
509
510
    def validated(self):
        """
        Get validated compounds
        """
        return (
            super()
            .get_queryset()
            .exclude(compoundaction__ppi__contribution__validated=False)
        )

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
511

512
class Compound(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
513
514
515
    """
    Chemical compound
    """
516

517
    objects = CompoundsManager()
518

519
    canonical_smile = models.TextField(verbose_name="Canonical Smiles", unique=True)
520
    is_macrocycle = models.BooleanField(
521
522
        verbose_name=_("is_macrocycle_verbose_name"),
        help_text=_("is_macrocycle_help_text"),
523
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
524
    aromatic_ratio = models.DecimalField(
525
        verbose_name="Aromatic ratio",
526
527
        max_digits=3,
        decimal_places=2,
528
529
        blank=True,
        null=True,
530
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
531
    balaban_index = models.DecimalField(
532
        verbose_name="Balaban index",
533
534
        max_digits=3,
        decimal_places=2,
535
536
        blank=True,
        null=True,
537
538
    )
    fsp3 = models.DecimalField(
539
        verbose_name="Fsp3", max_digits=3, decimal_places=2, blank=True, null=True
540
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
541
    gc_molar_refractivity = models.DecimalField(
542
        verbose_name="GC Molar Refractivity",
543
544
        max_digits=5,
        decimal_places=2,
545
546
        blank=True,
        null=True,
547
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
548
    log_d = models.DecimalField(
549
        verbose_name="LogD (Partition coefficient octanol-1/water, with pKa information)",
550
551
        max_digits=4,
        decimal_places=2,
552
553
        blank=True,
        null=True,
554
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
555
    a_log_p = models.DecimalField(
556
        verbose_name="ALogP (Partition coefficient octanol-1/water)",
557
558
        max_digits=4,
        decimal_places=2,
559
560
        blank=True,
        null=True,
561
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
562
    mean_atom_vol_vdw = models.DecimalField(
563
        verbose_name="Mean atom volume computed with VdW radii",
564
565
        max_digits=4,
        decimal_places=2,
566
567
        blank=True,
        null=True,
568
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
569
    molecular_weight = models.DecimalField(
570
        verbose_name="Molecular weight",
571
572
        max_digits=6,
        decimal_places=2,
573
574
        blank=True,
        null=True,
575
576
    )
    nb_acceptor_h = models.IntegerField(
577
        verbose_name="Number of hydrogen bond acceptors", blank=True, null=True
578
579
    )
    nb_aliphatic_amines = models.IntegerField(
580
        verbose_name="Number of aliphatics amines", blank=True, null=True
581
582
    )
    nb_aromatic_bonds = models.IntegerField(
583
        verbose_name="Number of aromatic bonds", blank=True, null=True
584
585
    )
    nb_aromatic_ether = models.IntegerField(
586
        verbose_name="Number of aromatic ethers", blank=True, null=True
587
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
588
    nb_aromatic_sssr = models.IntegerField(
589
        verbose_name="Number of aromatic Smallest Set of System Rings (SSSR)",
590
591
        blank=True,
        null=True,
592
    )
593
    nb_atom = models.IntegerField(verbose_name="Number of atoms", blank=True, null=True)
594
    nb_atom_non_h = models.IntegerField(
595
        verbose_name="Number of non hydrogen atoms", blank=True, null=True
596
597
    )
    nb_benzene_like_rings = models.IntegerField(
598
        verbose_name="Number of benzene-like rings", blank=True, null=True
599
600
    )
    nb_bonds = models.IntegerField(
601
        verbose_name="Number of bonds", blank=True, null=True
602
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
603
    nb_bonds_non_h = models.IntegerField(
604
        verbose_name="Number of bonds not involving a hydrogen", blank=True, null=True
605
606
    )
    nb_br = models.IntegerField(
607
        verbose_name="Number of Bromine atoms", blank=True, null=True
608
609
    )
    nb_c = models.IntegerField(
610
        verbose_name="Number of Carbon atoms", blank=True, null=True
611
612
    )
    nb_chiral_centers = models.IntegerField(
613
        verbose_name="Number of chiral centers", blank=True, null=True
614
615
    )
    nb_circuits = models.IntegerField(
616
        verbose_name="Number of circuits", blank=True, null=True
617
618
    )
    nb_cl = models.IntegerField(
619
        verbose_name="Number of Chlorine atoms", blank=True, null=True
620
621
    )
    nb_csp2 = models.IntegerField(
622
        verbose_name="Number of sp2-hybridized carbon atoms", blank=True, null=True
623
624
    )
    nb_csp3 = models.IntegerField(
625
        verbose_name="Number of sp3-hybridized carbon atoms", blank=True, null=True
626
627
    )
    nb_donor_h = models.IntegerField(
628
        verbose_name="Number of hydrogen bond donors", blank=True, null=True
629
630
    )
    nb_double_bonds = models.IntegerField(
631
        verbose_name="Number of double bonds", blank=True, null=True
632
633
    )
    nb_f = models.IntegerField(
634
        verbose_name="Number of fluorine atoms", blank=True, null=True
635
636
    )
    nb_i = models.IntegerField(
637
        verbose_name="Number of iodine atoms", blank=True, null=True
638
639
    )
    nb_multiple_bonds = models.IntegerField(
640
        verbose_name="Number of multiple bonds", blank=True, null=True
641
642
    )
    nb_n = models.IntegerField(
643
        verbose_name="Number of nitrogen atoms", blank=True, null=True
644
645
    )
    nb_o = models.IntegerField(
646
        verbose_name="Number of oxygen atoms", blank=True, null=True
647
648
    )
    nb_rings = models.IntegerField(
649
        verbose_name="Number of rings", blank=True, null=True
650
651
    )
    nb_rotatable_bonds = models.IntegerField(
652
        verbose_name="Number of rotatable bonds", blank=True, null=True
653
    )
654
655
    inchi = models.TextField(verbose_name="InChi", blank=True, null=True)
    inchikey = models.TextField(verbose_name="InChiKey", blank=True, null=True)
Hervé  MENAGER's avatar
Hervé MENAGER committed
656
    randic_index = models.DecimalField(
657
        verbose_name="Randic index",
658
659
        max_digits=4,
        decimal_places=2,
660
661
        blank=True,
        null=True,
662
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
663
    rdf070m = models.DecimalField(
664
        verbose_name="RDF070m, radial distribution function weighted by the atomic masses at 7Å",
665
666
        max_digits=5,
        decimal_places=2,
667
668
        blank=True,
        null=True,
669
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
670
    rotatable_bond_fraction = models.DecimalField(
671
        verbose_name="Fraction of rotatable bonds",
672
673
        max_digits=3,
        decimal_places=2,
674
675
        blank=True,
        null=True,
676
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
677
    sum_atom_polar = models.DecimalField(
678
        verbose_name="Sum of atomic polarizabilities",
679
680
        max_digits=5,
        decimal_places=2,
681
682
        blank=True,
        null=True,
683
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
684
    sum_atom_vol_vdw = models.DecimalField(
685
        verbose_name="Sum of atom volumes computed with VdW radii",
686
687
        max_digits=6,
        decimal_places=2,
688
689
        blank=True,
        null=True,
690
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
691
    tpsa = models.DecimalField(
692
        verbose_name="Topological Polar Surface Area (TPSA)",
693
694
        max_digits=5,
        decimal_places=2,
695
696
        blank=True,
        null=True,
697
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
698
    ui = models.DecimalField(
699
        verbose_name="Unsaturation index",
700
701
        max_digits=4,
        decimal_places=2,
702
703
        blank=True,
        null=True,
704
705
    )
    wiener_index = models.IntegerField(
706
        verbose_name="Wiener index", blank=True, null=True
707
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
708
    common_name = models.CharField(
709
        verbose_name="Common name", max_length=20, blank=True, null=True
710
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
711
    pubchem_id = models.CharField(
712
        verbose_name="Pubchem ID", max_length=10, blank=True, null=True
713
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
714
    chemspider_id = models.CharField(
715
        verbose_name="Chemspider ID", unique=True, max_length=10, blank=True, null=True
716
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
717
    chembl_id = models.CharField(
718
        verbose_name="Chembl ID", max_length=30, blank=True, null=True
719
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
720
    iupac_name = models.TextField(verbose_name="IUPAC name", blank=True, null=True)
721
    ligand_id = models.CharField("PDB Ligand ID", max_length=3, blank=True, null=True)
722
    pubs = models.IntegerField(
723
724
        verbose_name="Number of publications", null=True, blank=True
    )
725
    best_activity = models.DecimalField(
726
727
728
729
730
        "Best activity", max_digits=12, decimal_places=10, null=True, blank=True
    )
    best_activity_ppi_family_name = models.CharField(
        "Best activity PPI family name", max_length=30, null=True, blank=True
    )
731
732
    families = models.TextField("PPI family names", null=True, blank=True)

733
734
735
736
737
738
739
740
741
742
743
744
    le = models.FloatField(verbose_name="Ligand efficiency", null=True, blank=True)
    lle = models.FloatField(verbose_name="Lipophilic efficiency", null=True, blank=True)
    lipinsky_mw = models.BooleanField("MW ok for Lipinsky", null=True, blank=True)
    lipinsky_hba = models.BooleanField(
        "Hydrogen bond acceptors ok for Lipinsky", null=True, blank=True
    )
    lipinsky_hbd = models.BooleanField(
        "Hydrogen bond donors ok for Lipinsky", null=True, blank=True
    )
    lipinsky_a_log_p = models.BooleanField(
        "A log P ok for Lipinsky", null=True, blank=True
    )
745
    lipinsky_score = models.IntegerField(
746
        verbose_name="Lipinsky score", null=True, blank=True
747
    )
748
    lipinsky = models.BooleanField("Lipinsky ok", null=True, blank=True)
749
    hba_hbd = models.IntegerField(
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
        verbose_name="Sum of Hydrogen bond acceptors and donors", null=True, blank=True
    )
    veber_hba_hbd = models.BooleanField("HBA+HBD ok for Veber", null=True, blank=True)
    veber_tpsa = models.BooleanField("TPSA ok for Veber", null=True, blank=True)
    veber_rb = models.BooleanField(
        "Rotatable bonds ok for Veber", null=True, blank=True
    )
    veber = models.BooleanField("Veber ok", null=True, blank=True)
    pfizer_a_log_p = models.BooleanField("A log P ok for Pfizer", null=True, blank=True)
    pfizer_tpsa = models.BooleanField("TPSA ok for Pfizer", null=True, blank=True)
    pfizer = models.BooleanField("Pfizer ok", null=True, blank=True)
    pdb_ligand_av = models.BooleanField("PDB ligand available", null=True, blank=True)
    inhibition_role = models.BooleanField("Inhibition role", null=True, blank=True)
    binding_role = models.BooleanField("Binding role", null=True, blank=True)
    stabilisation_role = models.BooleanField(
        "Stabilisation role", null=True, blank=True
    )
    celltest_av = models.BooleanField("Cellular tests performed", null=True, blank=True)
    inhitest_av = models.BooleanField(
        "Inhibition tests performed", null=True, blank=True
    )
    stabtest_av = models.BooleanField(
        "Stabilisation tests performed", null=True, blank=True
    )
    bindtest_av = models.BooleanField("Binding tests performed", null=True, blank=True)
    pktest_av = models.BooleanField(
        "Pharmacokinetic tests performed", null=True, blank=True
    )
    cytoxtest_av = models.BooleanField(
        "Cytotoxicity tests performed", null=True, blank=True
    )
    insilico_av = models.BooleanField(
        "In silico tests performed", null=True, blank=True
    )
    tests_av = models.IntegerField(
        verbose_name="Number of tests available", null=True, blank=True
    )
787

788
    class Meta:
789
        ordering = ["id"]
790
        indexes = [
791
792
793
794
795
796
797
798
799
800
801
802
803
            models.Index(fields=["molecular_weight"]),
            models.Index(fields=["a_log_p"]),
            models.Index(fields=["nb_donor_h"]),
            models.Index(fields=["nb_acceptor_h"]),
            models.Index(fields=["tpsa"]),
            models.Index(fields=["nb_rotatable_bonds"]),
            models.Index(fields=["nb_aromatic_sssr"]),
            models.Index(fields=["nb_chiral_centers"]),
            models.Index(fields=["fsp3"]),
            models.Index(fields=["pubs"]),
            models.Index(fields=["best_activity"]),
            models.Index(fields=["le"]),
            models.Index(fields=["lle"]),
804
        ]
805
        # default_manager_name = 'ippidb.models.ValidatedCompoundsManager'
806
807
808
809
810
811
812
813
814
815
        # indexes = [
        #     models.Index(fields=['lipinsky']),
        #     models.Index(fields=['veber']),
        #     models.Index(fields=['pfizer']),
        #     models.Index(fields=['pdb_ligand_av']),
        #     models.Index(fields=['inhibition_role']),
        #     models.Index(fields=['binding_role']),
        #     models.Index(fields=['stabilisation_role']),
        #     models.Index(fields=['binding_role']),
        # ]
816

817
818
819
    def compute_drugbank_compound_similarity(self):
        """ compute Tanimoto similarity to existing DrugBank compounds """
        self.save()
820
        # fingerprints to compute drugbank similarities are in settings module, default FP2
821
        fingerprinter = FingerPrinter(getattr(settings, "DRUGBANK_FINGERPRINTS", "FP2"))
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
822
823
        # 1. compute tanimoto for SMILES query vs all compounds
        smiles_dict = {c.id: c.canonical_smiles for c in DrugBankCompound.objects.all()}