models.py 66.8 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 boltons.iterutils import flatten, unique_iter
12
from django.conf import settings
13
from django.contrib.auth import get_user_model
14
from django.core.exceptions import ValidationError
15
from django.db import models, transaction
16
from django.db.models import FloatField, IntegerField, BooleanField
17
from django.db.models import Max, Count, F, Q, Case, When, Subquery, OuterRef, Exists
18
from django.db.models.functions import Cast
19
from django.urls import reverse
20
from django.utils.translation import ugettext_lazy as _
21
22
23
from django_celery_results.models import TaskResult
from django.dispatch import receiver
from django.db.models.signals import post_save
24
from polymorphic.models import PolymorphicModel
Hervé  MENAGER's avatar
Hervé MENAGER committed
25

26
from .utils import FingerPrinter, smi2inchi, smi2inchikey
27
28
29
30
31
32
33
34
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,
35
36
    get_chembl_id,
    get_pubchem_id,
37
    get_ligand_id,
38
    EntryNotFoundError,
39
)
40

Hervé  MENAGER's avatar
Hervé MENAGER committed
41

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

    class Meta:
        abstract = True

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

64
65
66
    def autofill(self):
        raise NotImplementedError()

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

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

80
81

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

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

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

127
128
129
    def is_autofill_done(self):
        return len(self.title) > 0

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

    def has_external_url(self):
135
        return self.source == "PM" or self.source == "DO"
136
137

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

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

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

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

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
187

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

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

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

207
208
209
    def __str__(self):
        return self.name

Hervé  MENAGER's avatar
Hervé MENAGER committed
210
211
    class Meta:
        verbose_name_plural = "taxonomies"
Hervé  MENAGER's avatar
Hervé MENAGER committed
212

Hervé  MENAGER's avatar
Hervé MENAGER committed
213

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

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

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

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

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

239
240
241
    def __str__(self):
        return self.description

Hervé  MENAGER's avatar
Hervé MENAGER committed
242

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

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

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

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

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

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

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

302
    def is_autofill_done(self):
303
        return len(self.entry_name) > 0
304

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
310

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

    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
322

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

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

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

338
339
340
341
    @property
    def name(self):
        return self.pfam_id

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
345

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


368
class ProteinDomainComplex(PolymorphicModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
369
    """
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
370
    Protein-Domain association
Hervé  MENAGER's avatar
Hervé MENAGER committed
371
    """
372
373
374
375

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

Hervé  MENAGER's avatar
Hervé MENAGER committed
380
381
    class Meta:
        verbose_name_plural = "complexes"
382

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

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

394

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

    pass


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

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

410
411
412
413
    @property
    def complex_type(self):
        return "Bound"

414
415
    class Meta:
        verbose_name_plural = "bound complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
416

417
418
419
420
421
422
423
424
425
426
427
428
429
    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
430

431
class ProteinDomainPartnerComplex(ProteinDomainComplex):
Hervé  MENAGER's avatar
Hervé MENAGER committed
432
433
434
    """
    Protein-Domain association with a "partner complex" role
    """
Hervé  MENAGER's avatar
Hervé MENAGER committed
435

436
437
438
439
    @property
    def complex_type(self):
        return "Partner"

440
441
    class Meta:
        verbose_name_plural = "partner complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
442

443
444
445
446
447
    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
448

449
class Symmetry(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
450
451
452
    """
    Symmetry of a PPI
    """
453
454
455

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

457
458
459
    class Meta:
        verbose_name_plural = "symmetries"

460
    def __str__(self):
461
        return "{} ({})".format(self.code, self.description)
462

463
464

class Disease(models.Model):
465
466
    name = models.CharField("Disease", max_length=256)
    identifier = models.CharField("Identifier", max_length=32, null=True, blank=True)
467
468

    def __str__(self):
469
        return "%s (%s)" % (self.name, self.identifier)
470

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
471

Hervé  MENAGER's avatar
Hervé MENAGER committed
472
class PpiFamily(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
473
474
475
    """
    PPI Family
    """
476
477

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

479
480
481
    class Meta:
        verbose_name_plural = "PPI Families"

Hervé  MENAGER's avatar
Hervé MENAGER committed
482
483
    def __str__(self):
        return self.name
Hervé  MENAGER's avatar
Hervé MENAGER committed
484

Hervé  MENAGER's avatar
Hervé MENAGER committed
485

486
class Ppi(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
487
488
489
    """
    PPI
    """
490
491

    pdb_id = models.CharField("PDB ID", max_length=4, null=True, blank=True)
Hervé  MENAGER's avatar
Hervé MENAGER committed
492
    pockets_nb = models.IntegerField(
493
494
        "Total number of pockets in the complex", default=1
    )
495
    symmetry = models.ForeignKey(Symmetry, models.CASCADE)
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
496
    diseases = models.ManyToManyField(Disease, blank=True)
497
    family = models.ForeignKey(PpiFamily, models.CASCADE, null=True, blank=True)
498
    name = models.TextField("PPI name")
Hervé  MENAGER's avatar
Hervé MENAGER committed
499

500
    def __str__(self):
501
        return "PPI #{} on {}".format(self.id, self.name)
502

503
    def get_absolute_url(self):
504
        return reverse("ppi-view", kwargs={"ppi_pk": self.pk})
505

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
506
    def is_autofill_done(self):
507
        return self.name != ""
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
508

509
510
511
    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()
512
513
514
515
516

    def get_ppi_bound_complexes(self):
        """
        return bound ppi complexes belonging to this ppi
        """
517
        return PpiComplex.objects.filter(
518
519
            ppi=self, complex__in=ProteinDomainBoundComplex.objects.all()
        )
520

521
    def compute_name_from_protein_names(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
522
        all_protein_names = set(
523
524
            [
                ppi_complex.complex.protein.short_name
525
526
527
                for ppi_complex in self.ppicomplex_set.all()
            ]
        )
Hervé  MENAGER's avatar
Hervé MENAGER committed
528
        bound_protein_names = set(
529
530
            [
                ppi_complex.complex.protein.short_name
531
532
533
                for ppi_complex in self.get_ppi_bound_complexes()
            ]
        )
534
        partner_protein_names = all_protein_names - bound_protein_names
535
536
        bound_str = ",".join(sorted(bound_protein_names))
        partner_str = ",".join(sorted(partner_protein_names))
537
        name = bound_str
538
539
        if partner_str != "":
            name += " / " + partner_str
540
        return name
541

Hervé  MENAGER's avatar
Hervé MENAGER committed
542

Hervé  MENAGER's avatar
Hervé MENAGER committed
543
class PpiComplex(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
544
545
546
    """
    PPI Complex
    """
547

548
549
    ppi = models.ForeignKey(Ppi, models.CASCADE)
    complex = models.ForeignKey(ProteinDomainComplex, models.CASCADE)
550
    cc_nb = models.IntegerField(verbose_name=_("cc_nb_verbose_name"), default=1)
Hervé  MENAGER's avatar
Hervé MENAGER committed
551
552
553
554

    class Meta:
        verbose_name_plural = "Ppi complexes"

555
    def __str__(self):
556
557
        return "PPI {}, Complex {} ({})".format(self.ppi, self.complex, self.cc_nb)

558

559
class CompoundsManager(models.Manager):
Hervé  MENAGER's avatar
Hervé MENAGER committed
560
561
562
563
564
    """
    Model manager for the `Compound` class
    provides selections to `validated` or `user-visible` compounds
    """

565
    def for_user(self, current_user):
566
567
568
569
570
        """
        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
        """
571
572
573
        qs = self.get_queryset()
        if current_user.is_anonymous:
            qs = qs.exclude(compoundaction__ppi__contribution__validated=False)
574
            qs = qs.filter(replaced_with__isnull=True)
575
576
577
578
579
        elif not current_user.is_superuser:
            qs = qs.exclude(
                Q(compoundaction__ppi__contribution__validated=False),
                ~Q(compoundaction__ppi__contribution__contributor=current_user),
            )
580
            qs = qs.filter(replaced_with__isnull=True)
581
        return qs
582

583
584
585
586
587
588
589
590
591
592
    def validated(self):
        """
        Get validated compounds
        """
        return (
            super()
            .get_queryset()
            .exclude(compoundaction__ppi__contribution__validated=False)
        )

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
593

594
class Compound(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
595
596
597
    """
    Chemical compound
    """
598

599
    objects = CompoundsManager()
600

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

816
817
818
819
820
821
822
823
824
825
826
827
    le = models.FloatField(verbose_name="Ligand efficiency", null=True, bl