models.py 54.2 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
Hervé  MENAGER's avatar
Hervé MENAGER committed
9

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

20
from .utils import FingerPrinter, smi2inchi, smi2inchikey
21
22
23
24
25
26
27
28
29
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,
)
30

Hervé  MENAGER's avatar
Hervé MENAGER committed
31

32
33
34
35
class AutoFillableModel(models.Model):
    """
    AutoFillableModel makes it possible to automatically fill model fields from
    external sources in the autofill() method
36
37
    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
38
39
40
41
42
43
    """

    class Meta:
        abstract = True

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

54
55
56
    def autofill(self):
        raise NotImplementedError()

57
58
    def autofill_post_save(self):
        """
59
60
        method called after the save is done, usefull for setting m2m
        relations
61
62
63
64
        :return:
        """
        pass

65
66
67
    def is_autofill_done(self):
        return True

68
69

class Bibliography(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
70
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
71
72
    Bibliography references
    (publications or patents)
Hervé  MENAGER's avatar
Hervé MENAGER committed
73
    """
74
75

    SOURCES = (("PM", "PubMed ID"), ("PT", "Patent"), ("DO", "DOI"))
76
    id_source_validators = dict(
77
78
79
        PM=re.compile(r"^[0-9]+$"),
        PT=re.compile(r"^.*$"),
        DO=re.compile(r"^10.\d{4,9}/.+$"),
80
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
81
    source = models.CharField(
82
83
84
85
86
87
88
89
90
91
92
93
94
95
        "Bibliographic type", max_length=2, choices=SOURCES, default=SOURCES[0][0]
    )
    id_source = models.CharField("Bibliographic ID", max_length=25)
    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
96

97
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
98
99
100
101
        """
        fetch information from external services
        (Pubmed or Google patents)
        """
102
        if self.source == "PM":
103
            info = get_pubmed_info(self.id_source)
104
        elif self.source == "PT":
105
            info = get_google_patent_info(self.id_source)
106
        elif self.source == "DO":
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
107
108
109
            info = get_doi_info(self.id_source)
        else:
            raise NotImplementedError()
110
111
112
113
        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
114

115
116
117
    def is_autofill_done(self):
        return len(self.title) > 0

118
119
    def clean(self):
        super().clean()
120
121
122
        Bibliography.validate_source_id(self.id_source, self.source)

    def has_external_url(self):
123
        return self.source == "PM" or self.source == "DO"
124
125

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
145
    class Meta:
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
        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
163

164
    def __str__(self):
165
        return "{}, {}".format(self.source, self.id_source)
166

167
    def get_absolute_url(self):
168
        return reverse("biblio-view", kwargs={"biblio_pk": self.pk})
169

Hervé  MENAGER's avatar
Hervé MENAGER committed
170

171
class Taxonomy(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
172
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
173
    Taxonomy IDs (from NCBI Taxonomy)
Hervé  MENAGER's avatar
Hervé MENAGER committed
174
175
    and the corresponding human-readable name
    """
176

Hervé  MENAGER's avatar
Hervé MENAGER committed
177
    taxonomy_id = models.DecimalField(
178
179
180
        "NCBI TaxID", unique=True, max_digits=9, decimal_places=0
    )
    name = models.CharField("Organism name", max_length=200)
181

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

190
191
192
    def __str__(self):
        return self.name

Hervé  MENAGER's avatar
Hervé MENAGER committed
193
194
    class Meta:
        verbose_name_plural = "taxonomies"
Hervé  MENAGER's avatar
Hervé MENAGER committed
195

Hervé  MENAGER's avatar
Hervé MENAGER committed
196

197
class MolecularFunction(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
198
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
199
    Molecular functions (from Gene Ontology)
Hervé  MENAGER's avatar
Hervé MENAGER committed
200
201
    and the corresponding human-readable description
    """
202
203

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

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

215
216
217
    def is_autofill_done(self):
        return self.description is not None and len(self.description) > 0

218
219
    @property
    def name(self):
220
        return self.go_id + " " + self.description
221

222
223
224
    def __str__(self):
        return self.description

Hervé  MENAGER's avatar
Hervé MENAGER committed
225

226
class Protein(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
227
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
228
    Protein information (from Uniprot)
Hervé  MENAGER's avatar
Hervé MENAGER committed
229
230
    and the corresponding human-readable name
    """
231
232

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

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

252
        gene_names = info["gene_names"]
253
        # put whatever name it find
254
        self.gene_name = gene_names[0]["name"]
255
256
257
258
259
260
        # 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

261
262
        self.entry_name = info["entry_name"]
        self.short_name = info["short_name"]
263
        try:
264
            taxonomy = Taxonomy.objects.get(taxonomy_id=info["organism"])
265
266
        except Taxonomy.DoesNotExist:
            taxonomy = Taxonomy()
267
            taxonomy.taxonomy_id = info["organism"]
268
            taxonomy.save(autofill=True)
269
        self.organism = taxonomy
270
        self.__info = info
271

272
273
    def autofill_post_save(self):
        info = self.__info
274
275
        for go_id in info["molecular_functions"]:
            mol_function, created = MolecularFunction.objects.get_or_create(go_id=go_id)
276
            self.molecular_functions.add(mol_function)
277

278
        for domain_id in info["domains"]:
279
            domain, created = Domain.objects.get_or_create(pfam_acc=domain_id)
280
281
            self.domains.add(domain)

282
    def is_autofill_done(self):
283
        return len(self.entry_name) > 0
284

285
    def __str__(self):
286
        return "{} ({})".format(self.uniprot_id, self.recommended_name_long)
287

Hervé  MENAGER's avatar
Hervé MENAGER committed
288

289
class Domain(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
290
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
291
    Domain (i.e. Protein domain) information (from PFAM)
Hervé  MENAGER's avatar
Hervé MENAGER committed
292
    """
293
294
295
296
297
298
299

    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
300

Hervé  MENAGER's avatar
Hervé MENAGER committed
301
302
    # TODO: what is this field? check database
    # contents
303

304
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
305
306
307
308
        """
        fetch information from external services
        (PFAM)
        """
309
        info = get_pfam_info(self.pfam_acc)
310
311
        self.pfam_id = info["id"]
        self.pfam_description = info["description"]
Hervé  MENAGER's avatar
Hervé MENAGER committed
312

313
314
315
    def is_autofill_done(self):
        return self.pfam_id is not None and len(self.pfam_id) > 0

316
317
318
319
    @property
    def name(self):
        return self.pfam_id

320
    def __str__(self):
321
        return f"{self.pfam_acc} ({self.pfam_id}-{self.pfam_description})"
322

Hervé  MENAGER's avatar
Hervé MENAGER committed
323

324
class ProteinDomainComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
325
    """
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
326
    Protein-Domain association
Hervé  MENAGER's avatar
Hervé MENAGER committed
327
    """
328
329
330
331

    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
332

Hervé  MENAGER's avatar
Hervé MENAGER committed
333
334
    class Meta:
        verbose_name_plural = "complexes"
335

336
    def __str__(self):
337
338
339
        return "{} {}-{}".format(
            self.protein.short_name, self.protein_id, self.domain_id
        )
340

341
342
    def name(self):
        return self.protein.short_name
Hervé  MENAGER's avatar
Hervé MENAGER committed
343

344

345
class ProteinDomainBoundComplex(ProteinDomainComplex):
Hervé  MENAGER's avatar
Hervé MENAGER committed
346
347
348
    """
    Protein-Domain association with a "bound complex" role
    """
349
350

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

352
353
    class Meta:
        verbose_name_plural = "bound complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
354
355


356
class ProteinDomainPartnerComplex(ProteinDomainComplex):
Hervé  MENAGER's avatar
Hervé MENAGER committed
357
358
359
    """
    Protein-Domain association with a "partner complex" role
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
360

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
364

365
class Symmetry(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
366
367
368
    """
    Symmetry of a PPI
    """
369
370
371

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

373
374
375
    class Meta:
        verbose_name_plural = "symmetries"

376
    def __str__(self):
377
        return "{} ({})".format(self.code, self.description)
378

379
380

class Disease(models.Model):
381
382
    name = models.CharField("Disease", max_length=256)
    identifier = models.CharField("Identifier", max_length=32, null=True, blank=True)
383
384

    def __str__(self):
385
        return "%s (%s)" % (self.name, self.identifier)
386

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
387

Hervé  MENAGER's avatar
Hervé MENAGER committed
388
class PpiFamily(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
389
390
391
    """
    PPI Family
    """
392
393

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

395
396
397
    class Meta:
        verbose_name_plural = "PPI Families"

Hervé  MENAGER's avatar
Hervé MENAGER committed
398
399
    def __str__(self):
        return self.name
Hervé  MENAGER's avatar
Hervé MENAGER committed
400

Hervé  MENAGER's avatar
Hervé MENAGER committed
401

402
class Ppi(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
403
404
405
    """
    PPI
    """
406
407

    pdb_id = models.CharField("PDB ID", max_length=4, null=True, blank=True)
Hervé  MENAGER's avatar
Hervé MENAGER committed
408
    pockets_nb = models.IntegerField(
409
410
        "Total number of pockets in the complex", default=1
    )
411
    symmetry = models.ForeignKey(Symmetry, models.CASCADE)
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
412
    diseases = models.ManyToManyField(Disease, blank=True)
413
414
    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
415

416
    def __str__(self):
417
        return "PPI #{} on {}".format(self.id, self.name)
418

419
    def get_absolute_url(self):
420
        return reverse("ppi-view", kwargs={"ppi_pk": self.pk})
421

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
422
    def is_autofill_done(self):
423
        return self.name != ""
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
424

425
426
427
    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()
428
429
430
431
432

    def get_ppi_bound_complexes(self):
        """
        return bound ppi complexes belonging to this ppi
        """
433
        return PpiComplex.objects.filter(
434
435
            ppi=self, complex__in=ProteinDomainBoundComplex.objects.all()
        )
436

437
    def compute_name_from_protein_names(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
438
        all_protein_names = set(
439
440
            [
                ppi_complex.complex.protein.short_name
441
442
443
                for ppi_complex in self.ppicomplex_set.all()
            ]
        )
Hervé  MENAGER's avatar
Hervé MENAGER committed
444
        bound_protein_names = set(
445
446
            [
                ppi_complex.complex.protein.short_name
447
448
449
                for ppi_complex in self.get_ppi_bound_complexes()
            ]
        )
450
        partner_protein_names = all_protein_names - bound_protein_names
451
452
        bound_str = ",".join(bound_protein_names)
        partner_str = ",".join(partner_protein_names)
453
        name = bound_str
454
455
        if partner_str != "":
            name += " / " + partner_str
456
        return name
457

Hervé  MENAGER's avatar
Hervé MENAGER committed
458

Hervé  MENAGER's avatar
Hervé MENAGER committed
459
class PpiComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
460
461
462
    """
    PPI Complex
    """
463

464
465
    ppi = models.ForeignKey(Ppi, models.CASCADE)
    complex = models.ForeignKey(ProteinDomainComplex, models.CASCADE)
466
    cc_nb = models.IntegerField(verbose_name=_("cc_nb_verbose_name"), default=1)
Hervé  MENAGER's avatar
Hervé MENAGER committed
467
468
469
470

    class Meta:
        verbose_name_plural = "Ppi complexes"

471
    def __str__(self):
472
473
        return "PPI {}, Complex {} ({})".format(self.ppi, self.complex, self.cc_nb)

474

475
class ValidatedCompoundsManager(models.Manager):
Hervé  MENAGER's avatar
Hervé MENAGER committed
476
    """
477
478
479
    ValidatedCompoundManager filters only compounds from validated
    contributions (or not coming from contributions) in the results
    of the database query
Hervé  MENAGER's avatar
Hervé MENAGER committed
480
    """
481

482
    def get_queryset(self):
483
484
485
486
487
        return (
            super()
            .get_queryset()
            .exclude(compoundaction__ppi__contribution__validated=False)
        )
488

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
489

490
class Compound(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
491
492
493
    """
    Chemical compound
    """
494
495
496
497

    objects = models.Manager()
    validated = ValidatedCompoundsManager()

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

767
    class Meta:
768
        ordering = ["id"]
769
        indexes = [
770
771
772
773
774
775
776
777
778
779
780
781
782
            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"]),
783
        ]
784
        # default_manager_name = 'ippidb.models.ValidatedCompoundsManager'
785
786
787
788
789
790
791
792
793
794
        # 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']),
        # ]
795

796
797
798
    def compute_drugbank_compound_similarity(self):
        """ compute Tanimoto similarity to existing DrugBank compounds """
        self.save()
799
        # fingerprints to compute drugbank similarities are in settings module, default FP2
800
        fingerprinter = FingerPrinter(getattr(settings, "DRUGBANK_FINGERPRINTS", "FP2"))
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
801
802
        # 1. compute tanimoto for SMILES query vs all compounds
        smiles_dict = {c.id: c.canonical_smiles for c in DrugBankCompound.objects.all()}
803
        tanimoto_dict = fingerprinter.tanimoto_smiles(self.canonical_smile, smiles_dict)
804
805
806
        tanimoto_dict = dict(
            sorted(tanimoto_dict.items(), key=operator.itemgetter(1), reverse=True)[:15]
        )
807
808
        dbcts = []
        for id_, tanimoto in tanimoto_dict.items():
809
810
811
812
813
814
815
            dbcts.append(
                DrugbankCompoundTanimoto(
                    compound=self,
                    drugbank_compound=DrugBankCompound.objects.get(id=id_),
                    tanimoto=tanimoto,
                )
            )
816
817