models.py 54.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
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
        "Bibliographic type", max_length=2, choices=SOURCES, default=SOURCES[0][0]
    )
84
    id_source = models.TextField("Bibliographic ID")
85
86
87
88
89
90
91
92
93
94
95
    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
255
256
257
        try:
            self.gene_name = gene_names[0]["name"]
        except IndexError:
            pass
258
259
260
261
262
263
        # 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

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

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

281
        for domain_id in info["domains"]:
282
            domain, created = Domain.objects.get_or_create(pfam_acc=domain_id)
283
284
            self.domains.add(domain)

285
    def is_autofill_done(self):
286
        return len(self.entry_name) > 0
287

288
    def __str__(self):
289
290
291
        return "{} ({})".format(
            self.uniprot_id, self.recommended_name_long or self.short_name
        )
292

Hervé  MENAGER's avatar
Hervé MENAGER committed
293

294
class Domain(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
295
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
296
    Domain (i.e. Protein domain) information (from PFAM)
Hervé  MENAGER's avatar
Hervé MENAGER committed
297
    """
298
299
300
301
302
303
304

    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
305

Hervé  MENAGER's avatar
Hervé MENAGER committed
306
307
    # TODO: what is this field? check database
    # contents
308

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

318
319
320
    def is_autofill_done(self):
        return self.pfam_id is not None and len(self.pfam_id) > 0

321
322
323
324
    @property
    def name(self):
        return self.pfam_id

325
    def __str__(self):
326
        return f"{self.pfam_acc} ({self.pfam_id}-{self.pfam_description})"
327

Hervé  MENAGER's avatar
Hervé MENAGER committed
328

329
class ProteinDomainComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
330
    """
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
331
    Protein-Domain association
Hervé  MENAGER's avatar
Hervé MENAGER committed
332
    """
333
334
335
336

    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
337

Hervé  MENAGER's avatar
Hervé MENAGER committed
338
339
    class Meta:
        verbose_name_plural = "complexes"
340

341
    def __str__(self):
342
343
344
        return "{} {}-{}".format(
            self.protein.short_name, self.protein_id, self.domain_id
        )
345

346
347
    def name(self):
        return self.protein.short_name
Hervé  MENAGER's avatar
Hervé MENAGER committed
348

349

350
class ProteinDomainBoundComplex(ProteinDomainComplex):
Hervé  MENAGER's avatar
Hervé MENAGER committed
351
352
353
    """
    Protein-Domain association with a "bound complex" role
    """
354
355

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

357
358
    class Meta:
        verbose_name_plural = "bound complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
359
360


361
class ProteinDomainPartnerComplex(ProteinDomainComplex):
Hervé  MENAGER's avatar
Hervé MENAGER committed
362
363
364
    """
    Protein-Domain association with a "partner complex" role
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
365

366
367
    class Meta:
        verbose_name_plural = "partner complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
368

Hervé  MENAGER's avatar
Hervé MENAGER committed
369

370
class Symmetry(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
371
372
373
    """
    Symmetry of a PPI
    """
374
375
376

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

378
379
380
    class Meta:
        verbose_name_plural = "symmetries"

381
    def __str__(self):
382
        return "{} ({})".format(self.code, self.description)
383

384
385

class Disease(models.Model):
386
387
    name = models.CharField("Disease", max_length=256)
    identifier = models.CharField("Identifier", max_length=32, null=True, blank=True)
388
389

    def __str__(self):
390
        return "%s (%s)" % (self.name, self.identifier)
391

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
392

Hervé  MENAGER's avatar
Hervé MENAGER committed
393
class PpiFamily(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
394
395
396
    """
    PPI Family
    """
397
398

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

400
401
402
    class Meta:
        verbose_name_plural = "PPI Families"

Hervé  MENAGER's avatar
Hervé MENAGER committed
403
404
    def __str__(self):
        return self.name
Hervé  MENAGER's avatar
Hervé MENAGER committed
405

Hervé  MENAGER's avatar
Hervé MENAGER committed
406

407
class Ppi(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
408
409
410
    """
    PPI
    """
411
412

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

421
    def __str__(self):
422
        return "PPI #{} on {}".format(self.id, self.name)
423

424
    def get_absolute_url(self):
425
        return reverse("ppi-view", kwargs={"ppi_pk": self.pk})
426

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
427
    def is_autofill_done(self):
428
        return self.name != ""
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
429

430
431
432
    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()
433
434
435
436
437

    def get_ppi_bound_complexes(self):
        """
        return bound ppi complexes belonging to this ppi
        """
438
        return PpiComplex.objects.filter(
439
440
            ppi=self, complex__in=ProteinDomainBoundComplex.objects.all()
        )
441

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
463

Hervé  MENAGER's avatar
Hervé MENAGER committed
464
class PpiComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
465
466
467
    """
    PPI Complex
    """
468

469
470
    ppi = models.ForeignKey(Ppi, models.CASCADE)
    complex = models.ForeignKey(ProteinDomainComplex, models.CASCADE)
471
    cc_nb = models.IntegerField(verbose_name=_("cc_nb_verbose_name"), default=1)
Hervé  MENAGER's avatar
Hervé MENAGER committed
472
473
474
475

    class Meta:
        verbose_name_plural = "Ppi complexes"

476
    def __str__(self):
477
478
        return "PPI {}, Complex {} ({})".format(self.ppi, self.complex, self.cc_nb)

479

480
481
482
483
484
485
486
487
488
489
490
class CompoundsManager(models.Manager):
    def for_user(self, current_user):
        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
491

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
492

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

498
    objects = CompoundsManager()
499

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

716
717
718
719
720
721
722
723
724
725
726
727
    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
    )
728
    lipinsky_score = models.IntegerField(
729
        verbose_name="Lipinsky score", null=True, blank=True
730
    )
731
    lipinsky = models.BooleanField("Lipinsky ok", null=True, blank=True)
732
    hba_hbd = models.IntegerField(
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
766
767
768
769
        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
    )
770

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

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