models.py 19.3 KB
Newer Older
Hervé  MENAGER's avatar
Hervé MENAGER committed
1
2
3
4
from __future__ import unicode_literals

from django.db import models

5
from .ws import get_pubmed_info, get_epo_info, get_uniprot_info, get_taxonomy_info, get_go_info, get_pfam_info
6

7
8
9
10
11
12
13
14
15
16
17
18
19
20
class AutoFillableModel(models.Model):
    """
    AutoFillableModel makes it possible to automatically fill model fields from
    external sources in the autofill() method
    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
    """

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        if kwargs.get('autofill') is True:
            self.autofill()
21
22
        if 'autofill' in kwargs:
            del kwargs['autofill']
Hervé  MENAGER's avatar
Hervé MENAGER committed
23
        super(AutoFillableModel, self).save(*args, **kwargs)
24
25
26


class Bibliography(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
27
28
29
30
    """
    Bibliography data table
    """
    SOURCES = (
31
32
33
        ('PM', 'PubMed ID'),
        ('PT', 'Patent'),
        ('DO', 'DOI ID')
Hervé  MENAGER's avatar
Hervé MENAGER committed
34
    )
35
    source = models.CharField('Bibliographic type', max_length=2, choices=SOURCES, default='PM')
Hervé  MENAGER's avatar
Hervé MENAGER committed
36
37
    id_source = models.CharField('Bibliographic ID', max_length=25)
    title = models.CharField('Title', max_length=300)
38
    journal_name = models.CharField('Journal name', max_length=50, null=True)
Hervé  MENAGER's avatar
Hervé MENAGER committed
39
40
    authors_list = models.CharField('Authors list', max_length=500)
    biblio_year = models.PositiveSmallIntegerField('Year')
41
    cytotox = models.BooleanField('Cytotoxicity data', default=False)
Rachel TORCHET's avatar
Rachel TORCHET committed
42
43
44
45
46
47
    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
48

49
    def autofill(self):
50
51
52
53
54
55
56
57
        if self.source == 'PM':
            info = get_pubmed_info(self.id_source)
        else:
            info = get_epo_info(self.id_source)
        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
58

Hervé  MENAGER's avatar
Hervé MENAGER committed
59
60
61
    class Meta:
        verbose_name_plural = "bibliographies"

62
63
    def __str__(self):
        return '{}, {}'.format(self.source, self.id_source)
64

Hervé  MENAGER's avatar
Hervé MENAGER committed
65

66
class Taxonomy(AutoFillableModel):
67
    taxonomy_id = models.DecimalField('NCBI TaxID', unique=True, max_digits=9, decimal_places=0)
Hervé  MENAGER's avatar
Hervé MENAGER committed
68
    name = models.CharField('Organism name', max_length=200)
69

70
    def autofill(self):
71
72
73
        info = get_taxonomy_info(self.taxonomy_id)
        self.name = info['scientific_name']

74
75
76
    def __str__(self):
        return self.name

Hervé  MENAGER's avatar
Hervé MENAGER committed
77
78
    class Meta:
        verbose_name_plural = "taxonomies"
Hervé  MENAGER's avatar
Hervé MENAGER committed
79

80
class MolecularFunction(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
81
82
83
    go_id = models.CharField('Gene Ontology ID', unique=True, max_length=10) # GO term id format: 'GO:0000000' 
    description = models.CharField('description', max_length=500)

84
    def autofill(self):
85
86
87
        info = get_go_info(self.go_id)
        self.description = info['label']

88
89
90
    def __str__(self):
        return self.description

91
class Protein(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
92
93
94
95
96
    uniprot_id = models.CharField('Uniprot ID', unique=True, max_length=10)
    recommended_name_long = models.CharField('Uniprot Recommended Name (long)', max_length=75)
    short_name = models.CharField('Short name', max_length=50)
    gene_name = models.CharField('Gene name', unique=True, max_length=30)
    entry_name = models.CharField('Entry name', max_length=30)
97
    organism = models.ForeignKey('Taxonomy', default='Homo sapiens')
Hervé  MENAGER's avatar
Hervé MENAGER committed
98
99
    molecular_functions = models.ManyToManyField(MolecularFunction)

100
    def autofill(self):
101
        info = get_uniprot_info(self.uniprot_id)
102
        self.recommended_name_long = info['recommended_name']
103
104
105
106
107
108
109
        self.gene_name = info['gene']
        self.entry_name = info['entry_name']
        try:
            taxonomy = Taxonomy.objects.get(taxonomy_id=info['organism'])
        except Taxonomy.DoesNotExist:
            taxonomy = Taxonomy()
            taxonomy.taxonomy_id = info['organism']
110
            taxonomy.save(autofill=True)
111
        self.organism = taxonomy
112
        super(Protein, self).save()
113
114
115
116
117
118
        for go_id in info['molecular_functions']:
            try:
                mol_function = MolecularFunction.objects.get(go_id=go_id)
            except MolecularFunction.DoesNotExist:
                mol_function = MolecularFunction()
                mol_function.go_id = go_id
119
                mol_function.save(autofill=True)
120
            self.molecular_functions.add(mol_function)
121

122
123
124
    def __str__(self):
        return '{} ({})'.format(self.uniprot_id, self.recommended_name_long)

125
class Domain(AutoFillableModel):
Hervé  MENAGER's avatar
Hervé MENAGER committed
126
127
128
    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)
129
130
    domain_family = models.CharField('Domain family', max_length=25)  #TODO: what is this field? check database contents

131
    def autofill(self):
132
133
134
        info = get_pfam_info(self.pfam_acc)
        self.pfam_id = info['id']
        self.pfam_description = info['description']
Hervé  MENAGER's avatar
Hervé MENAGER committed
135

136
137
138
    def __str__(self):
        return '{} ({}-{})'.format(self.pfam_acc, self.pfam_id, self.pfam_description)

139
class ProteinDomainComplex(models.Model):
140
141
    protein = models.ForeignKey('Protein')
    domain = models.ForeignKey('Domain')
142
143
    ppc_copy_nb = models.IntegerField('Number of copies of the protein in the complex')
    
Hervé  MENAGER's avatar
Hervé MENAGER committed
144
145
    class Meta:
        verbose_name_plural = "complexes"
146

147
148
149
    def __str__(self):
        return '{}-{}'.format(self.protein_id, self.domain_id)

150
151
152
153
class ProteinDomainBoundComplex(ProteinDomainComplex):
    ppp_copy_nb_per_p = models.IntegerField('Number of copies of the protein in the pocket')
    class Meta:
        verbose_name_plural = "bound complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
154
    
155
156
157
class ProteinDomainPartnerComplex(ProteinDomainComplex):
    class Meta:
        verbose_name_plural = "partner complexes"
Hervé  MENAGER's avatar
Hervé MENAGER committed
158

159
160
161
class Symmetry(models.Model):
    code = models.CharField('Symmetry code', max_length=2)
    description = models.CharField('Description', max_length=300)
Hervé  MENAGER's avatar
Hervé MENAGER committed
162

163
164
165
    class Meta:
        verbose_name_plural = "symmetries"

166
167
168
    def __str__(self):
        return '{} ({})'.format(self.code, self.description)

169
170
171
172
173
174
175

class Disease(models.Model):
    name = models.CharField('Disease', max_length=30, unique=True) # is there any database/nomenclature for diseases?

    def __str__(self):
        return self.name

176
class Ppi(models.Model):
177
    pdb_id = models.CharField('PDB ID', max_length=4, null=True)
178
    pockets_nb = models.IntegerField('Total number of pockets in the complex', default=1)
179
    symmetry = models.ForeignKey(Symmetry)
180
    diseases = models.ManyToManyField(Disease)
Hervé  MENAGER's avatar
Hervé MENAGER committed
181

182
183
184
    def __str__(self):
        return '{} PPI, PDB:{}'.format(self.symmetry.description, self.pdb_id or 'unknown')

185
186
187
188
189
190
191
192
193
194
195
196
197
    def get_ppi_complexes(self):
        """
        return all ppi complexes belonging to this ppi
        """
        return PpiComplex.objects.filter(ppi=self)

    def get_ppi_bound_complexes(self):
        """
        return bound ppi complexes belonging to this ppi
        """
        #this is the less efficient query ever seen, FIXME
        return PpiComplex.objects.filter(ppi=self, complex__in=ProteinDomainBoundComplex.objects.all())

198

Hervé  MENAGER's avatar
Hervé MENAGER committed
199
200
201
202
203
204
205
206
class PpiComplex(models.Model):
    ppi = models.ForeignKey(Ppi)
    complex = models.ForeignKey(ProteinDomainComplex)
    cc_nb = models.IntegerField('Number of copies of the complex in the PPI', default=1)

    class Meta:
        verbose_name_plural = "Ppi complexes"

207
208
209
    def __str__(self):
        return 'PPI {}, Complex {} ({})'.format(self.ppi, self.complex, self.cc_nb)

Hervé  MENAGER's avatar
Hervé MENAGER committed
210

211
class Compound(models.Model):
Hervé  MENAGER's avatar
Hervé MENAGER committed
212
    canonical_smile = models.CharField('Canonical Smile', unique=True, max_length=500)
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
    is_macrocycle = models.BooleanField('Contains one or more macrocycles') 
    aromatic_ratio = models.DecimalField('Aromatic ratio', max_digits=3, decimal_places=2)
    balaban_index = models.DecimalField('Balaban index', max_digits=3, decimal_places=2)  
    fsp3 = models.DecimalField('Fsp3', max_digits=3, decimal_places=2)
    gc_molar_refractivity = models.DecimalField('GC Molar Refractivity', max_digits=5, decimal_places=2)
    log_d = models.DecimalField('LogD (Partition coefficient octanol-1/water, with pKa information)', max_digits=4, decimal_places=2)
    a_log_p = models.DecimalField('ALogP (Partition coefficient octanol-1/water)', max_digits=4, decimal_places=2)
    mean_atom_vol_vdw = models.DecimalField('Mean atom volume computed with VdW radii', max_digits=4, decimal_places=2)
    molecular_weight = models.DecimalField('Molecular weight', max_digits=6, decimal_places=2)
    nb_acceptor_h = models.IntegerField('Number of hydrogen bond acceptors')
    nb_aliphatic_amines = models.IntegerField('Number of aliphatics amines')
    nb_aromatic_bonds = models.IntegerField('Number of aromatic bonds')
    nb_aromatic_ether = models.IntegerField('Number of aromatic ethers')
    nb_aromatic_sssr = models.IntegerField('Number of aromatic Smallest Set of System Rings (SSSR)')
    nb_atom = models.IntegerField('Number of atoms') 
    nb_atom_non_h = models.IntegerField('Number of non hydrogen atoms') 
    nb_benzene_like_rings = models.IntegerField('Number of benzene-like rings')
    nb_bonds = models.IntegerField('Number of bonds')
    nb_bonds_non_h = models.IntegerField('Number of bonds not involving a hydrogen')  
    nb_br = models.IntegerField('Number of Bromine atoms')  
    nb_c = models.IntegerField('Number of Carbon atoms')  
    nb_chiral_centers = models.IntegerField('Number of chiral centers')  
    nb_circuits = models.IntegerField('Number of circuits')  
    nb_cl = models.IntegerField('Number of Chlorine atoms')  
    nb_csp2 = models.IntegerField('Number of sp2-hybridized carbon atoms')  
    nb_csp3 = models.IntegerField('Number of sp3-hybridized carbon atoms')  
    nb_donor_h = models.IntegerField('Number of hydrogen bond donors')  
    nb_double_bonds = models.IntegerField('Number of double bonds')  
    nb_f = models.IntegerField('Number of fluorine atoms')  
    nb_i = models.IntegerField('Number of iodine atoms')  
    nb_multiple_bonds = models.IntegerField('Number of multiple bonds')  
    nb_n = models.IntegerField('Number of nitrogen atoms')
    nb_o = models.IntegerField('Number of oxygen atoms')  
    nb_rings = models.IntegerField('Number of rings')  
    nb_rotatable_bonds = models.IntegerField('Number of rotatable bonds')  
    randic_index = models.DecimalField('Randic index', max_digits=4, decimal_places=2)  
    rdf070m = models.DecimalField('RDF070m, radial distribution function weighted by the atomic masses at 7Å', max_digits=5, decimal_places=2)  
    rotatable_bond_fraction = models.DecimalField('Fraction of rotatable bonds', max_digits=3, decimal_places=2)  
    sum_atom_polar = models.DecimalField('Sum of atomic polarizabilities', max_digits=5, decimal_places=2)  
    sum_atom_vol_vdw = models.DecimalField('Sum of atom volumes computed with VdW radii', max_digits=6, decimal_places=2)  
    tpsa = models.DecimalField('Topological Polar Surface Area (TPSA)', max_digits=5, decimal_places=2)  
    ui = models.DecimalField('Unsaturation index', max_digits=4, decimal_places=2)  
    wiener_index = models.IntegerField('Wiener index')  
    common_name = models.CharField('Common name', unique=True, max_length=20, blank=True, null=True)  
257
    pubchem_id = models.CharField('Pubchem ID', max_length=10, blank=True, null=True)  
258
    chemspider_id = models.CharField('Chemspider ID', unique=True, max_length=10, blank=True, null=True)  
259
260
    chembl_id = models.CharField('Chembl ID', max_length=30, blank=True, null=True)  
    iupac_name = models.CharField('IUPAC name', max_length=255, blank=True, null=True)  
261
    mddr_compound = models.ForeignKey('MDDRCompoundImport', blank=True, null=True)  
262
263


264
class MDDRActivityClass(models.Model):
265
    name = models.CharField('Activity Class', max_length=100, unique=True)  
266
267
268
269
270

    class Meta:
        verbose_name_plural = "MDDR activity classes"

    def __str__(self):
271
        return self.name
272

273
class MDDRCompoundImport(models.Model):
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293

    MDDR_DEVELOPMENT_PHASES = (
        ('Biological Testing',''),
        ('Preclinical',''),
        ('Phase III',''),
        ('Phase II',''),
        ('Phase I/II',''),
        ('Phase I',''),
        ('Launched',''),
        ('Pre-Registered',''),
        ('Not Applicable',''),
        ('Discontinued',''),
        ('Clinical',''),
        ('Withdrawn',''),
        ('Registered',''),
        ('Not Determined',''),
        ('Phase II/III',''),
        ('IND Filed',''),
    )

294
    mddr_name = models.CharField('MDDR name', max_length=40)  
295
    dvpmt_phase = models.CharField('Development phase', max_length=20, choices=MDDR_DEVELOPMENT_PHASES)  
296
    canonical_smile = models.CharField('Canonical Smile', max_length=500, blank=True, null=True)  
297
    #TODO index this table on canonical_smile
298
    db_import_date = models.DateTimeField('MDDR release year/month')  
299
    activity_classes = models.ManyToManyField(MDDRActivityClass)
Hervé  MENAGER's avatar
Hervé MENAGER committed
300

301
302
303
    class Meta:
        # over multiple releases of the MDDR database, the same compound can evolve in its development phase
        # the same compound can have different names and development phases in the same MDDR release
304
        unique_together = (('mddr_name', 'dvpmt_phase', 'canonical_smile'),)
Hervé  MENAGER's avatar
Hervé MENAGER committed
305
        verbose_name_plural = "MDDR compound imports"
Hervé  MENAGER's avatar
Hervé MENAGER committed
306

307
308
309
    def __str__(self):
        return "{}, {}".format(self.mddr_name, self.dvpmt_phase)

310
311
312
313
class MDDRSimilarity(models.Model):
    canonical_smile_ippidb = models.CharField('Canonical Smile for IPPIDB compound', max_length=500, unique=True, blank=True, null=True)
    canonical_smile_mddr = models.CharField('Canonical Smile for MDDR Compound', max_length=500, unique=True, blank=True, null=True)
    tanimoto = models.DecimalField('Tanimoto', max_digits=6, decimal_places=5)  
Hervé  MENAGER's avatar
Hervé MENAGER committed
314
315

    class Meta:
316
        unique_together = (('canonical_smile_ippidb', 'canonical_smile_mddr'),)
Hervé  MENAGER's avatar
Hervé MENAGER committed
317
        verbose_name_plural = "MDDR similarities"
318

319
320
class CellLine(models.Model):
    name = models.CharField('Name', max_length=50, unique=True)
Hervé  MENAGER's avatar
Hervé MENAGER committed
321

322
323
324
    def __str__(self):
        return self.name

325
326
327
328
329
330
331
332
333
334
class TestActivityDescription(models.Model):
    TEST_TYPES = (
        ('BIOCH', 'Biochemical assay'),
        ('CELL', 'Cellular assay')
    )
    TEST_MODULATION_TYPES = (
        ('B', 'Binding'),
        ('I', 'Inhibition'),
        ('S', 'Stabilization')
    )
335
336
    biblio = models.ForeignKey(Bibliography)
    ppi = models.ForeignKey(Ppi, blank=True, null=True)  
337
338
339
340
    test_name = models.CharField('Test name', max_length=100)  
    test_type = models.CharField('Test type', max_length=5, choices=TEST_TYPES)
    test_modulation_type = models.CharField('Test modulation type', max_length=1, choices=TEST_MODULATION_TYPES)
    nb_active_compounds = models.IntegerField('Total number of active compounds')  
341
    cell_line = models.ForeignKey(CellLine, blank=True, null=True)
342

Hervé  MENAGER's avatar
Hervé MENAGER committed
343
    def get_complexes(self):
344
345
346
347
348
349
350
351
        """
        get the complexes tested for this PPI
        depends on the modulation type
        """
        if self.test_modulation_type=='I':
            return self.ppi.get_ppi_complexes()
        else:
            return self.ppi.get_ppi_bound_complexes()
352
353

class CompoundActivityResult(models.Model):
354
355
356
357
358
    MODULATION_TYPES = (
        ('B', 'Binding'),
        ('I', 'Inhibition'),
        ('S', 'Stabilization')
    )
Hervé  MENAGER's avatar
Hervé MENAGER committed
359
360
361
362
363
364
    ACTIVITY_TYPES = (
        ('pIC50','pIC50 (half maximal inhibitory concentration, -log10)'),
        ('pEC50','pEC50 (half maximal effective concentration, -log10)'),
        ('pKd','pKd (dissociation constant, -log10)'),
        ('pKi','pKi (inhibition constant, -log10)'),
    )
365
366
    compound = models.ForeignKey(Compound)  
    test_activity_description = models.ForeignKey(TestActivityDescription)  
Hervé  MENAGER's avatar
Hervé MENAGER committed
367
    activity_type = models.CharField('Activity type', max_length=5, choices=ACTIVITY_TYPES)
368
    activity = models.DecimalField('Activity', max_digits=12, decimal_places=10)  
369
    modulation_type = models.CharField('Modulation type', max_length=1, choices=MODULATION_TYPES)  
Hervé  MENAGER's avatar
Hervé MENAGER committed
370
371

    class Meta:
372
        unique_together = (('compound', 'test_activity_description', 'activity_type'),)
Hervé  MENAGER's avatar
Hervé MENAGER committed
373

374
class TestCytotoxDescription(models.Model):
375
    biblio = models.ForeignKey(Bibliography)  
376
377
378
    test_name = models.CharField('Cytotoxicity test name', max_length=100)  
    cell_line = models.ForeignKey(CellLine)
    compound_concentration = models.DecimalField('Compound concentration in μM', max_digits=7, decimal_places=3, blank=True, null=True)  
Hervé  MENAGER's avatar
Hervé MENAGER committed
379

380
class CompoundCytotoxicityResult(models.Model):
381
382
    compound = models.ForeignKey(Compound)  
    test_cytotoxicity_description = models.ForeignKey(TestCytotoxDescription)
383
    toxicity = models.BooleanField('Toxicity', default=False)
Hervé  MENAGER's avatar
Hervé MENAGER committed
384
385

    class Meta:
386
        unique_together = (('compound', 'test_cytotoxicity_description'),)
387
388

class TestPKDescription(models.Model):
389
390
391
392
393
394
    ADMINISTRATION_MODES = (
        ('IV', ''),
        ('PO', ''),
        ('IP', ''),
        ('SL', 'SL')
    )
395
    biblio = models.ForeignKey(Bibliography)  
396
397
    test_name = models.CharField('Pharmacokinetic test name', max_length=100)  
    organism = models.ForeignKey(Taxonomy)
398
    administration_mode = models.CharField('Administration mode', max_length=2, choices=ADMINISTRATION_MODES,blank=True, null=True)  
399
400
401
402
    dose = models.DecimalField('Dose in mg/kg', max_digits=7, decimal_places=4, blank=True, null=True)  
    dose_interval = models.IntegerField('Dose interval, in hours', blank=True, null=True)  

class CompoundPKResult(models.Model):
403
404
    compound = models.ForeignKey(Compound)  
    test_pk_description = models.ForeignKey(TestPKDescription)
405
406
407
408
409
410
411
412
    tolerated = models.NullBooleanField('Tolerated', null=True)
    auc = models.IntegerField('Area under curve (ng.mL-1.hr)', blank=True, null=True)  
    clearance = models.DecimalField('Clearance (mL/hr)', max_digits=7, decimal_places=3, blank=True, null=True)  
    cmax = models.DecimalField('Maximal concentration (ng/mL)', max_digits=7, decimal_places=3, blank=True, null=True)  
    oral_bioavailability = models.IntegerField('Oral Bioavailability (%F)', blank=True, null=True)  
    t_demi = models.IntegerField('t½', blank=True, null=True)  
    t_max = models.IntegerField('tmax', blank=True, null=True)  
    voldistribution = models.DecimalField('Volume distribution (Vd)', max_digits=5, decimal_places=2, blank=True, null=True)  
Hervé  MENAGER's avatar
Hervé MENAGER committed
413
414

    class Meta:
415
        unique_together = (('compound', 'test_pk_description'),)
Hervé  MENAGER's avatar
Hervé MENAGER committed
416
417


418
class CompoundAction(models.Model):
419
420
    ACTIVATION_MODES = (
        ('O', 'Orthosteric'),
421
        ('A', 'Allosteric')
422
    )
423
    compound = models.ForeignKey(Compound)  
424
    activation_mode = models.CharField('Activation mode', max_length=1, choices=ACTIVATION_MODES)  
425
    ppi = models.ForeignKey(Ppi)
426
427
    pdb_id = models.CharField('PDB ID', max_length=4)
    nb_copy_compounds = models.IntegerField('Number of copies for the compound')  
Hervé  MENAGER's avatar
Hervé MENAGER committed
428
429

    class Meta:
Hervé  MENAGER's avatar
Hervé MENAGER committed
430
431
432
        unique_together = (('ppi', 'compound', 'activation_mode', 'pdb_id'),)

    def get_complexes(self):
433
434
435
436
        """
        get the complexes involved in the compound action
        which are always the bound complexes
        """
437
        return self.ppi.get_ppi_bound_complexes()
Hervé  MENAGER's avatar
Hervé MENAGER committed
438

439
class RefCompoundBiblio(models.Model):
440
441
    compound = models.ForeignKey(Compound)  
    bibliography = models.ForeignKey(Bibliography)  
442
    compound_name = models.CharField('Compound name in the publication', max_length=50)  
Hervé  MENAGER's avatar
Hervé MENAGER committed
443
444

    class Meta:
445
        unique_together = (('compound', 'bibliography'),)