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
35
    get_chembl_id,
    get_pubchem_id,
    EntryNotFoundError,
36
)
37

Hervé  MENAGER's avatar
Hervé MENAGER committed
38

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

    class Meta:
        abstract = True

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

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

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

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

77
78

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

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

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

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

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

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

    def get_external_url(self):
135
136
137
        """
        Get URL to the publication
        """
138
        if self.source == "PM":
139
140
141
142
143
            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}"
144
145
146
147
148

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

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

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
184

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

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

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

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
210

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

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

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

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

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
239

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

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

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

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

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

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

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

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
307

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

    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
319

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

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

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

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
342

343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
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)


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

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

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

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

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

391

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

    pass


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

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

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

410
411
412
413
414
415
416
417
418
419
420
421
422
    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
423

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

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

432
433
434
435
436
    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
437

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

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

446
447
448
    class Meta:
        verbose_name_plural = "symmetries"

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

452
453

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

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

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
460

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

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

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
474

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

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

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

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

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

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

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
531

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

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

    class Meta:
        verbose_name_plural = "Ppi complexes"

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

547

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

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

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

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
580

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

586
    objects = CompoundsManager()
587

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

803
804
805
806
807
808
809
810
811
812
813
814
    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
    )
815
    lipinsky_score = models.IntegerField(
816
        verbose_name="Lipinsky score", null=True, blank=True
817
    )
818
    lipinsky = models.BooleanField("Lipinsky ok", null=True, blank=True)
819
    hba_hbd = models.IntegerField(
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854