models.py 61.9 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
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,
33
34
    get_chembl_id,
    get_pubchem_id,
35
    get_ligand_id,
36
    EntryNotFoundError,
37
)
38

Hervé  MENAGER's avatar
Hervé MENAGER committed
39

40
41
class AutoFillableModel(models.Model):
    """
42
43
    AutoFillableModel abstract model to enable automated model fields
    filling from external sources in the autofill() method.
44
45
    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
46
47
48
49
50
51
    """

    class Meta:
        abstract = True

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

62
63
64
    def autofill(self):
        raise NotImplementedError()

65
66
    def autofill_post_save(self):
        """
67
68
        Method called automatically after the save is done,
        usefull for setting m2m relations
69
70
71
        """
        pass

72
    def is_autofill_done(self):
73
74
75
        """
        test whether or not the model has been already autofilled
        """
76
77
        return True

78
79

class Bibliography(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
80
    """
81
    Bibliography reference
Hervé  MENAGER's avatar
Hervé MENAGER committed
82
    (publications or patents)
Hervé  MENAGER's avatar
Hervé MENAGER committed
83
    """
84
85

    SOURCES = (("PM", "PubMed ID"), ("PT", "Patent"), ("DO", "DOI"))
86
    id_source_validators = dict(
87
88
89
        PM=re.compile(r"^[0-9]+$"),
        PT=re.compile(r"^.*$"),
        DO=re.compile(r"^10.\d{4,9}/.+$"),
90
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
91
    source = models.CharField(
92
93
        "Bibliographic type", max_length=2, choices=SOURCES, default=SOURCES[0][0]
    )
94
    id_source = models.TextField("Bibliographic ID")
95
96
97
98
99
100
101
102
103
104
105
    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
106

107
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
108
109
110
111
        """
        fetch information from external services
        (Pubmed or Google patents)
        """
112
        if self.source == "PM":
113
            info = get_pubmed_info(self.id_source)
114
        elif self.source == "PT":
115
            info = get_google_patent_info(self.id_source)
116
        elif self.source == "DO":
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
117
118
119
            info = get_doi_info(self.id_source)
        else:
            raise NotImplementedError()
120
121
122
123
        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
124

125
126
127
    def is_autofill_done(self):
        return len(self.title) > 0

128
129
    def clean(self):
        super().clean()
130
131
132
        Bibliography.validate_source_id(self.id_source, self.source)

    def has_external_url(self):
133
        return self.source == "PM" or self.source == "DO"
134
135

    def get_external_url(self):
136
137
138
        """
        Get URL to the publication
        """
139
        if self.source == "PM":
140
141
142
143
144
            return f"https://www.ncbi.nlm.nih.gov/pubmed/{self.id_source}"
        elif self.source == "DO":
            return f"https://doi.org/{self.id_source}"
        elif self.source == "PT":
            return f"https://patentscope.wipo.int/search/en/detail.jsf?docId={self.id_source}"
145
146
147
148
149

    @staticmethod
    def validate_source_id(id_source, source):
        id_source_validator = Bibliography.id_source_validators[source]
        if not id_source_validator.match(id_source):
150
151
            raise ValidationError(
                dict(
152
153
                    id_source=_(
                        f"Must match pattern {id_source_validator.pattern}"
154
155
                        " for this selected source"
                    )
156
157
                )
            )
158
        return True
159

Hervé  MENAGER's avatar
Hervé MENAGER committed
160
    class Meta:
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
        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
178

179
    def __str__(self):
180
        return "{}, {}".format(self.source, self.id_source)
181

182
    def get_absolute_url(self):
183
        return reverse("biblio-view", kwargs={"biblio_pk": self.pk})
184

Hervé  MENAGER's avatar
Hervé MENAGER committed
185

186
class Taxonomy(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
187
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
188
    Taxonomy IDs (from NCBI Taxonomy)
Hervé  MENAGER's avatar
Hervé MENAGER committed
189
190
    and the corresponding human-readable name
    """
191

Hervé  MENAGER's avatar
Hervé MENAGER committed
192
    taxonomy_id = models.DecimalField(
193
194
195
        "NCBI TaxID", unique=True, max_digits=9, decimal_places=0
    )
    name = models.CharField("Organism name", max_length=200)
196

197
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
198
199
200
201
        """
        fetch information from external services
        (NCBI Entrez)
        """
202
        info = get_taxonomy_info(self.taxonomy_id)
203
        self.name = info["scientific_name"]
204

205
206
207
    def __str__(self):
        return self.name

Hervé  MENAGER's avatar
Hervé MENAGER committed
208
209
    class Meta:
        verbose_name_plural = "taxonomies"
Hervé  MENAGER's avatar
Hervé MENAGER committed
210

Hervé  MENAGER's avatar
Hervé MENAGER committed
211

212
class MolecularFunction(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
213
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
214
    Molecular functions (from Gene Ontology)
Hervé  MENAGER's avatar
Hervé MENAGER committed
215
216
    and the corresponding human-readable description
    """
217
218

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

222
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
223
224
225
226
        """
        fetch information from external services
        (EBI OLS)
        """
227
        info = get_go_info(self.go_id)
228
        self.description = info["label"]
229

230
231
232
    def is_autofill_done(self):
        return self.description is not None and len(self.description) > 0

233
234
    @property
    def name(self):
235
        return self.go_id + " " + self.description
236

237
238
239
    def __str__(self):
        return self.description

Hervé  MENAGER's avatar
Hervé MENAGER committed
240

241
class Protein(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
242
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
243
    Protein information (from Uniprot)
Hervé  MENAGER's avatar
Hervé MENAGER committed
244
245
    and the corresponding human-readable name
    """
246
247

    uniprot_id = models.CharField("Uniprot ID", unique=True, max_length=10)
Hervé  MENAGER's avatar
Hervé MENAGER committed
248
    recommended_name_long = models.CharField(
249
        "Uniprot Recommended Name (long)", max_length=75, blank=True, null=True
250
251
    )
    short_name = models.CharField("Short name", max_length=50)
252
    gene_name = models.CharField("Gene name", max_length=30, blank=True, null=True)
253
254
    entry_name = models.CharField("Entry name", max_length=30)
    organism = models.ForeignKey("Taxonomy", models.CASCADE)
Hervé  MENAGER's avatar
Hervé MENAGER committed
255
    molecular_functions = models.ManyToManyField(MolecularFunction)
256
    domains = models.ManyToManyField("Domain")
Hervé  MENAGER's avatar
Hervé MENAGER committed
257

258
    @transaction.atomic
259
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
260
261
262
263
        """
        fetch information from external services
        (Uniprot) and create Taxonomy/Molecular Functions if needed
        """
264
        info = get_uniprot_info(self.uniprot_id)
265
        self.recommended_name_long = info["recommended_name"]
266

267
        gene_names = info["gene_names"]
268
        # put whatever name it find
269
270
271
272
        try:
            self.gene_name = gene_names[0]["name"]
        except IndexError:
            pass
273
274
275
276
277
278
        # 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

279
280
        self.entry_name = info["entry_name"]
        self.short_name = info["short_name"]
281
        try:
282
            taxonomy = Taxonomy.objects.get(taxonomy_id=info["organism"])
283
284
        except Taxonomy.DoesNotExist:
            taxonomy = Taxonomy()
285
            taxonomy.taxonomy_id = info["organism"]
286
            taxonomy.save(autofill=True)
287
        self.organism = taxonomy
288
        self.__info = info
289

290
291
    def autofill_post_save(self):
        info = self.__info
292
293
        for go_id in info["molecular_functions"]:
            mol_function, created = MolecularFunction.objects.get_or_create(go_id=go_id)
294
            self.molecular_functions.add(mol_function)
295

296
        for domain_id in info["domains"]:
297
            domain, created = Domain.objects.get_or_create(pfam_acc=domain_id)
298
299
            self.domains.add(domain)

300
    def is_autofill_done(self):
301
        return len(self.entry_name) > 0
302

303
    def __str__(self):
304
305
306
        return "{} ({})".format(
            self.uniprot_id, self.recommended_name_long or self.short_name
        )
307

Hervé  MENAGER's avatar
Hervé MENAGER committed
308

309
class Domain(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
310
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
311
    Domain (i.e. Protein domain) information (from PFAM)
Hervé  MENAGER's avatar
Hervé MENAGER committed
312
    """
313
314
315
316
317
318
319

    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
320

Hervé  MENAGER's avatar
Hervé MENAGER committed
321
322
    # TODO: what is this field? check database
    # contents
323

324
    def autofill(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
325
326
327
328
        """
        fetch information from external services
        (PFAM)
        """
329
        info = get_pfam_info(self.pfam_acc)
330
331
        self.pfam_id = info["id"]
        self.pfam_description = info["description"]
Hervé  MENAGER's avatar
Hervé MENAGER committed
332

333
334
335
    def is_autofill_done(self):
        return self.pfam_id is not None and len(self.pfam_id) > 0

336
337
338
339
    @property
    def name(self):
        return self.pfam_id

340
    def __str__(self):
341
        return f"{self.pfam_acc} ({self.pfam_id}-{self.pfam_description})"
342

Hervé  MENAGER's avatar
Hervé MENAGER committed
343

344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
class ProteinDomainComplexGroup(models.Model):
    """
    Protein-Domain association group
    """

    protein = models.ForeignKey("Protein", models.CASCADE)
    domain = models.ForeignKey("Domain", models.CASCADE, null=True, blank=True)

    class Meta:
        verbose_name_plural = "protein domain complex groups"

    def __str__(self):
        return "{} {}-{}".format(
            self.protein.short_name,
            self.protein.uniprot_id,
            self.domain.pfam_id if self.domain else "unknow",
        )

    def name(self):
        return str(self)


366
class ProteinDomainComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
367
    """
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
368
    Protein-Domain association
Hervé  MENAGER's avatar
Hervé MENAGER committed
369
    """
370
371
372
373

    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")
374
375
376
    group = models.ForeignKey(
        "ProteinDomainComplexGroup", models.SET_NULL, null=True, blank=True
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
377

Hervé  MENAGER's avatar
Hervé MENAGER committed
378
379
    class Meta:
        verbose_name_plural = "complexes"
380

381
    def __str__(self):
382
383
384
385
386
        return "{} {}-{} ({})".format(
            self.protein.short_name,
            self.protein.uniprot_id,
            self.domain.pfam_id if self.domain else "unknow",
            self.ppc_copy_nb,
387
        )
388

389
390
    def name(self):
        return self.protein.short_name
Hervé  MENAGER's avatar
Hervé MENAGER committed
391

392

393
394
395
396
397
398
399
400
class ProteinDomainBoundComplexGroup(ProteinDomainComplexGroup):
    """
    Protein-Domain Bound Complexes group
    """

    pass


401
class ProteinDomainBoundComplex(ProteinDomainComplex):
Hervé  MENAGER's avatar
Hervé MENAGER committed
402
403
404
    """
    Protein-Domain association with a "bound complex" role
    """
405
406

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

408
409
    class Meta:
        verbose_name_plural = "bound complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
410

411
412
413
414
415
416
417
418
419
420
421
422
423
    def set_group(self):
        self.group = ProteinDomainBoundComplexGroup.objects.get_or_create(
            protein=self.protein, domain=self.domain
        )[0]


class ProteinDomainPartnerComplexGroup(ProteinDomainComplexGroup):
    """
    Protein-Domain Bound Complexes group
    """

    pass

Hervé  MENAGER's avatar
Hervé MENAGER committed
424

425
class ProteinDomainPartnerComplex(ProteinDomainComplex):
Hervé  MENAGER's avatar
Hervé MENAGER committed
426
427
428
    """
    Protein-Domain association with a "partner complex" role
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
429

430
431
    class Meta:
        verbose_name_plural = "partner complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
432

433
434
435
436
437
    def set_group(self):
        self.group = ProteinDomainPartnerComplexGroup.objects.get_or_create(
            protein=self.protein, domain=self.domain
        )[0]

Hervé  MENAGER's avatar
Hervé MENAGER committed
438

439
class Symmetry(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
440
441
442
    """
    Symmetry of a PPI
    """
443
444
445

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

447
448
449
    class Meta:
        verbose_name_plural = "symmetries"

450
    def __str__(self):
451
        return "{} ({})".format(self.code, self.description)
452

453
454

class Disease(models.Model):
455
456
    name = models.CharField("Disease", max_length=256)
    identifier = models.CharField("Identifier", max_length=32, null=True, blank=True)
457
458

    def __str__(self):
459
        return "%s (%s)" % (self.name, self.identifier)
460

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
461

Hervé  MENAGER's avatar
Hervé MENAGER committed
462
class PpiFamily(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
463
464
465
    """
    PPI Family
    """
466
467

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

469
470
471
    class Meta:
        verbose_name_plural = "PPI Families"

Hervé  MENAGER's avatar
Hervé MENAGER committed
472
473
    def __str__(self):
        return self.name
Hervé  MENAGER's avatar
Hervé MENAGER committed
474

Hervé  MENAGER's avatar
Hervé MENAGER committed
475

476
class Ppi(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
477
478
479
    """
    PPI
    """
480
481

    pdb_id = models.CharField("PDB ID", max_length=4, null=True, blank=True)
Hervé  MENAGER's avatar
Hervé MENAGER committed
482
    pockets_nb = models.IntegerField(
483
484
        "Total number of pockets in the complex", default=1
    )
485
    symmetry = models.ForeignKey(Symmetry, models.CASCADE)
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
486
    diseases = models.ManyToManyField(Disease, blank=True)
487
    family = models.ForeignKey(PpiFamily, models.CASCADE, null=True, blank=True)
488
    name = models.TextField("PPI name")
Hervé  MENAGER's avatar
Hervé MENAGER committed
489

490
    def __str__(self):
491
        return "PPI #{} on {}".format(self.id, self.name)
492

493
    def get_absolute_url(self):
494
        return reverse("ppi-view", kwargs={"ppi_pk": self.pk})
495

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
496
    def is_autofill_done(self):
497
        return self.name != ""
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
498

499
500
501
    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()
502
503
504
505
506

    def get_ppi_bound_complexes(self):
        """
        return bound ppi complexes belonging to this ppi
        """
507
        return PpiComplex.objects.filter(
508
509
            ppi=self, complex__in=ProteinDomainBoundComplex.objects.all()
        )
510

511
    def compute_name_from_protein_names(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
512
        all_protein_names = set(
513
514
            [
                ppi_complex.complex.protein.short_name
515
516
517
                for ppi_complex in self.ppicomplex_set.all()
            ]
        )
Hervé  MENAGER's avatar
Hervé MENAGER committed
518
        bound_protein_names = set(
519
520
            [
                ppi_complex.complex.protein.short_name
521
522
523
                for ppi_complex in self.get_ppi_bound_complexes()
            ]
        )
524
        partner_protein_names = all_protein_names - bound_protein_names
525
526
        bound_str = ",".join(sorted(bound_protein_names))
        partner_str = ",".join(sorted(partner_protein_names))
527
        name = bound_str
528
529
        if partner_str != "":
            name += " / " + partner_str
530
        return name
531

Hervé  MENAGER's avatar
Hervé MENAGER committed
532

Hervé  MENAGER's avatar
Hervé MENAGER committed
533
class PpiComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
534
535
536
    """
    PPI Complex
    """
537

538
539
    ppi = models.ForeignKey(Ppi, models.CASCADE)
    complex = models.ForeignKey(ProteinDomainComplex, models.CASCADE)
540
    cc_nb = models.IntegerField(verbose_name=_("cc_nb_verbose_name"), default=1)
Hervé  MENAGER's avatar
Hervé MENAGER committed
541
542
543
544

    class Meta:
        verbose_name_plural = "Ppi complexes"

545
    def __str__(self):
546
547
        return "PPI {}, Complex {} ({})".format(self.ppi, self.complex, self.cc_nb)

548

549
class CompoundsManager(models.Manager):
Hervé  MENAGER's avatar
Hervé MENAGER committed
550
551
552
553
554
    """
    Model manager for the `Compound` class
    provides selections to `validated` or `user-visible` compounds
    """

555
    def for_user(self, current_user):
556
557
558
559
560
        """
        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
        """
561
562
563
564
565
566
567
568
569
        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
570

571
572
573
574
575
576
577
578
579
580
    def validated(self):
        """
        Get validated compounds
        """
        return (
            super()
            .get_queryset()
            .exclude(compoundaction__ppi__contribution__validated=False)
        )

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
581

582
class Compound(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
583
584
585
    """
    Chemical compound
    """
586

587
    objects = CompoundsManager()
588

589
    canonical_smile = models.TextField(verbose_name="Canonical Smiles", unique=True)
590
    is_macrocycle = models.BooleanField(
591
592
        verbose_name=_("is_macrocycle_verbose_name"),
        help_text=_("is_macrocycle_help_text"),
593
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
594
    aromatic_ratio = models.DecimalField(
595
        verbose_name="Aromatic ratio",
596
597
        max_digits=3,
        decimal_places=2,
598
599
        blank=True,
        null=True,
600
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
601
    balaban_index = models.DecimalField(
602
        verbose_name="Balaban index",
603
604
        max_digits=3,
        decimal_places=2,
605
606
        blank=True,
        null=True,
607
608
    )
    fsp3 = models.DecimalField(
609
        verbose_name="Fsp3", max_digits=3, decimal_places=2, blank=True, null=True
610
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
611
    gc_molar_refractivity = models.DecimalField(
612
        verbose_name="GC Molar Refractivity",
613
614
        max_digits=5,
        decimal_places=2,
615
616
        blank=True,
        null=True,
617
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
618
    log_d = models.DecimalField(
619
        verbose_name="LogD (Partition coefficient octanol-1/water, with pKa information)",
620
621
        max_digits=4,
        decimal_places=2,
622
623
        blank=True,
        null=True,
624
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
625
    a_log_p = models.DecimalField(
626
        verbose_name="ALogP (Partition coefficient octanol-1/water)",
627
628
        max_digits=4,
        decimal_places=2,
629
630
        blank=True,
        null=True,
631
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
632
    mean_atom_vol_vdw = models.DecimalField(
633
        verbose_name="Mean atom volume computed with VdW radii",
634
635
        max_digits=4,
        decimal_places=2,
636
637
        blank=True,
        null=True,
638
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
639
    molecular_weight = models.DecimalField(
640
        verbose_name="Molecular weight",
641
642
        max_digits=6,
        decimal_places=2,
643
644
        blank=True,
        null=True,
645
646
    )
    nb_acceptor_h = models.IntegerField(
647
        verbose_name="Number of hydrogen bond acceptors", blank=True, null=True
648
649
    )
    nb_aliphatic_amines = models.IntegerField(
650
        verbose_name="Number of aliphatics amines", blank=True, null=True
651
652
    )
    nb_aromatic_bonds = models.IntegerField(
653
        verbose_name="Number of aromatic bonds", blank=True, null=True
654
655
    )
    nb_aromatic_ether = models.IntegerField(
656
        verbose_name="Number of aromatic ethers", blank=True, null=True
657
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
658
    nb_aromatic_sssr = models.IntegerField(
659
        verbose_name="Number of aromatic Smallest Set of System Rings (SSSR)",
660
661
        blank=True,
        null=True,
662
    )
663
    nb_atom = models.IntegerField(verbose_name="Number of atoms", blank=True, null=True)
664
    nb_atom_non_h = models.IntegerField(
665
        verbose_name="Number of non hydrogen atoms", blank=True, null=True
666
667
    )
    nb_benzene_like_rings = models.IntegerField(
668
        verbose_name="Number of benzene-like rings", blank=True, null=True
669
670
    )
    nb_bonds = models.IntegerField(
671
        verbose_name="Number of bonds", blank=True, null=True
672
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
673
    nb_bonds_non_h = models.IntegerField(
674
        verbose_name="Number of bonds not involving a hydrogen", blank=True, null=True
675
676
    )
    nb_br = models.IntegerField(
677
        verbose_name="Number of Bromine atoms", blank=True, null=True
678
679
    )
    nb_c = models.IntegerField(
680
        verbose_name="Number of Carbon atoms", blank=True, null=True
681
682
    )
    nb_chiral_centers = models.IntegerField(
683
        verbose_name="Number of chiral centers", blank=True, null=True
684
685
    )
    nb_circuits = models.IntegerField(
686
        verbose_name="Number of circuits", blank=True, null=True
687
688
    )
    nb_cl = models.IntegerField(
689
        verbose_name="Number of Chlorine atoms", blank=True, null=True
690
691
    )
    nb_csp2 = models.IntegerField(
692
        verbose_name="Number of sp2-hybridized carbon atoms", blank=True, null=True
693
694
    )
    nb_csp3 = models.IntegerField(
695
        verbose_name="Number of sp3-hybridized carbon atoms", blank=True, null=True
696
697
    )
    nb_donor_h = models.IntegerField(
698
        verbose_name="Number of hydrogen bond donors", blank=True, null=True
699
700
    )
    nb_double_bonds = models.IntegerField(
701
        verbose_name="Number of double bonds", blank=True, null=True
702
703
    )
    nb_f = models.IntegerField(
704
        verbose_name="Number of fluorine atoms", blank=True, null=True
705
706
    )
    nb_i = models.IntegerField(
707
        verbose_name="Number of iodine atoms", blank=True, null=True
708
709
    )
    nb_multiple_bonds = models.IntegerField(
710
        verbose_name="Number of multiple bonds", blank=True, null=True
711
712
    )
    nb_n = models.IntegerField(
713
        verbose_name="Number of nitrogen atoms", blank=True, null=True
714
715
    )
    nb_o = models.IntegerField(
716
        verbose_name="Number of oxygen atoms", blank=True, null=True
717
718
    )
    nb_rings = models.IntegerField(
719
        verbose_name="Number of rings", blank=True, null=True
720
721
    )
    nb_rotatable_bonds = models.IntegerField(
722
        verbose_name="Number of rotatable bonds", blank=True, null=True
723
    )
724
725
    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
726
    randic_index = models.DecimalField(
727
        verbose_name="Randic index",
728
729
        max_digits=4,
        decimal_places=2,
730
731
        blank=True,
        null=True,
732
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
733
    rdf070m = models.DecimalField(
734
        verbose_name="RDF070m, radial distribution function weighted by the atomic masses at 7Å",
735
736
        max_digits=5,
        decimal_places=2,
737
738
        blank=True,
        null=True,
739
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
740
    rotatable_bond_fraction = models.DecimalField(
741
        verbose_name="Fraction of rotatable bonds",
742
743
        max_digits=3,
        decimal_places=2,
744
745
        blank=True,
        null=True,
746
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
747
    sum_atom_polar = models.DecimalField(
748
        verbose_name="Sum of atomic polarizabilities",
749
750
        max_digits=5,
        decimal_places=2,
751
752
        blank=True,
        null=True,
753
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
754
    sum_atom_vol_vdw = models.DecimalField(
755
        verbose_name="Sum of atom volumes computed with VdW radii",
756
757
        max_digits=6,
        decimal_places=2,
758
759
        blank=True,
        null=True,
760
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
761
    tpsa = models.DecimalField(
762
        verbose_name="Topological Polar Surface Area (TPSA)",
763
764
        max_digits=5,
        decimal_places=2,
765
766
        blank=True,
        null=True,
767
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
768
    ui = models.DecimalField(
769
        verbose_name="Unsaturation index",
770
771
        max_digits=4,
        decimal_places=2,
772
773
        blank=True,
        null=True,
774
775
    )
    wiener_index = models.IntegerField(
776
        verbose_name="Wiener index", blank=True, null=True
777
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
778
    common_name = models.CharField(
779
        verbose_name="Common name", max_length=20, blank=True, null=True
780
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
781
    pubchem_id = models.CharField(
782
        verbose_name="Pubchem ID", max_length=10, blank=True, null=True
783
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
784
    chemspider_id = models.CharField(
785
        verbose_name="Chemspider ID", unique=True, max_length=10, blank=True, null=True
786
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
787
    chembl_id = models.CharField(
788
        verbose_name="Chembl ID", max_length=30, blank=True, null=True
789
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
790
    iupac_name = models.TextField(verbose_name="IUPAC name", blank=True, null=True)
791
    ligand_id = models.CharField("PDB Ligand ID", max_length=3, blank=True, null=True)
792
    drugbank_id = models.TextField("Drugbank ID", blank=True, null=True)
793
    pubs = models.IntegerField(
794
795
        verbose_name="Number of publications", null=True, blank=True
    )
796
    best_activity = models.DecimalField(
797
798
799
800
801
        "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
    )
802
803
    families = models.TextField("PPI family names", null=True, blank=True)

804
805
806
807
808
809
810
811
812
813
814
815
    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
    )
816
    lipinsky_score = models.IntegerField(
817
        verbose_name="Lipinsky score", null=True, blank=True
818
    )
819
    lipinsky = models.BooleanField("Lipinsky ok", null=True, blank=True)
820
    hba_hbd = models.IntegerField(
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835