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

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

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

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
35

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

    class Meta:
        abstract = True

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

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

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

68
    def is_autofill_done(self):
69
70
71
        """
        test whether or not the model has been already autofilled
        """
72
73
        return True

74
75

class Bibliography(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
76
    """
77
    Bibliography reference
Hervé  MENAGER's avatar
Hervé MENAGER committed
78
    (publications or patents)
Hervé  MENAGER's avatar
Hervé MENAGER committed
79
    """
80
81

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

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

121
122
123
    def is_autofill_done(self):
        return len(self.title) > 0

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

    def has_external_url(self):
129
        return self.source == "PM" or self.source == "DO"
130
131

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

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

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

175
    def __str__(self):
176
        return "{}, {}".format(self.source, self.id_source)
177

178
    def get_absolute_url(self):
179
        return reverse("biblio-view", kwargs={"biblio_pk": self.pk})
180

Hervé  MENAGER's avatar
Hervé MENAGER committed
181

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

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

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

201
202
203
    def __str__(self):
        return self.name

Hervé  MENAGER's avatar
Hervé MENAGER committed
204
205
    class Meta:
        verbose_name_plural = "taxonomies"
Hervé  MENAGER's avatar
Hervé MENAGER committed
206

Hervé  MENAGER's avatar
Hervé MENAGER committed
207

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

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

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

226
227
228
    def is_autofill_done(self):
        return self.description is not None and len(self.description) > 0

229
230
    @property
    def name(self):
231
        return self.go_id + " " + self.description
232

233
234
235
    def __str__(self):
        return self.description

Hervé  MENAGER's avatar
Hervé MENAGER committed
236

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

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

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

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

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

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

292
        for domain_id in info["domains"]:
293
            domain, created = Domain.objects.get_or_create(pfam_acc=domain_id)
294
295
            self.domains.add(domain)

296
    def is_autofill_done(self):
297
        return len(self.entry_name) > 0
298

299
    def __str__(self):
300
301
302
        return "{} ({})".format(
            self.uniprot_id, self.recommended_name_long or self.short_name
        )
303

Hervé  MENAGER's avatar
Hervé MENAGER committed
304

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

    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
316

Hervé  MENAGER's avatar
Hervé MENAGER committed
317
318
    # TODO: what is this field? check database
    # contents
319

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

329
330
331
    def is_autofill_done(self):
        return self.pfam_id is not None and len(self.pfam_id) > 0

332
333
334
335
    @property
    def name(self):
        return self.pfam_id

336
    def __str__(self):
337
        return f"{self.pfam_acc} ({self.pfam_id}-{self.pfam_description})"
338

Hervé  MENAGER's avatar
Hervé MENAGER committed
339

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


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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
374
375
    class Meta:
        verbose_name_plural = "complexes"
376

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

385
386
    def name(self):
        return self.protein.short_name
Hervé  MENAGER's avatar
Hervé MENAGER committed
387

388

389
390
391
392
393
394
395
396
class ProteinDomainBoundComplexGroup(ProteinDomainComplexGroup):
    """
    Protein-Domain Bound Complexes group
    """

    pass


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

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

404
405
    class Meta:
        verbose_name_plural = "bound complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
406

407
408
409
410
411
412
413
414
415
416
417
418
419
    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
420

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

426
427
    class Meta:
        verbose_name_plural = "partner complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
428

429
430
431
432
433
    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
434

435
class Symmetry(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
436
437
438
    """
    Symmetry of a PPI
    """
439
440
441

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

443
444
445
    class Meta:
        verbose_name_plural = "symmetries"

446
    def __str__(self):
447
        return "{} ({})".format(self.code, self.description)
448

449
450

class Disease(models.Model):
451
452
    name = models.CharField("Disease", max_length=256)
    identifier = models.CharField("Identifier", max_length=32, null=True, blank=True)
453
454

    def __str__(self):
455
        return "%s (%s)" % (self.name, self.identifier)
456

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
457

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

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

465
466
467
    class Meta:
        verbose_name_plural = "PPI Families"

Hervé  MENAGER's avatar
Hervé MENAGER committed
468
469
    def __str__(self):
        return self.name
Hervé  MENAGER's avatar
Hervé MENAGER committed
470

Hervé  MENAGER's avatar
Hervé MENAGER committed
471

472
class Ppi(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
473
474
475
    """
    PPI
    """
476
477

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

486
    def __str__(self):
487
        return "PPI #{} on {}".format(self.id, self.name)
488

489
    def get_absolute_url(self):
490
        return reverse("ppi-view", kwargs={"ppi_pk": self.pk})
491

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
492
    def is_autofill_done(self):
493
        return self.name != ""
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
494

495
496
497
    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()
498
499
500
501
502

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
528

Hervé  MENAGER's avatar
Hervé MENAGER committed
529
class PpiComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
530
531
532
    """
    PPI Complex
    """
533

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

    class Meta:
        verbose_name_plural = "Ppi complexes"

541
    def __str__(self):
542
543
        return "PPI {}, Complex {} ({})".format(self.ppi, self.complex, self.cc_nb)

544

545
546
class CompoundsManager(models.Manager):
    def for_user(self, current_user):
547
548
549
550
551
        """
        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
        """
552
553
554
555
556
557
558
559
560
        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
561

562
563
564
565
566
567
568
569
570
571
    def validated(self):
        """
        Get validated compounds
        """
        return (
            super()
            .get_queryset()
            .exclude(compoundaction__ppi__contribution__validated=False)
        )

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
572

573
class Compound(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
574
575
576
    """
    Chemical compound
    """
577

578
    objects = CompoundsManager()
579

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

794
795
796
797
798
799
800
801
802
803
804
805
    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
    )
806
    lipinsky_score = models.IntegerField(
807
        verbose_name="Lipinsky score", null=True, blank=True
808
    )
809
    lipinsky = models.BooleanField("Lipinsky ok", null=True, blank=True)
810
    hba_hbd = models.IntegerField(
811
812
813
814
815
816
817
818
819
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
        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(