views.py 20.9 KB
Newer Older
1
2
3
4
"""
iPPI-DB django views module
"""

5
import json
6
import math
7
from collections import OrderedDict
8

9
from django.db.models import Max, Min, Count, F, Q
10
11
from django.db.models.functions import Cast
from django.db.models import FloatField                                                                                                                                                                 
12
from django.shortcuts import render
13
from django.urls import reverse
14
from django.http import HttpResponseRedirect, Http404, JsonResponse
Hervé  MENAGER's avatar
Hervé MENAGER committed
15
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
16
from django.contrib.auth.mixins import LoginRequiredMixin
17
from django.views.generic.list import ListView
18
from django.views.generic.base import RedirectView
Hervé  MENAGER's avatar
Hervé MENAGER committed
19
from formtools.wizard.views import SessionWizardView, NamedUrlSessionWizardView
20
21

import ippidb
22
from .forms import *
23
from .utils import mol2smi, smi2mol
24
from .models import Protein, Bibliography, ProteinDomainComplex, ProteinDomainBoundComplex, RefCompoundBiblio, TestActivityDescription, Compound, Ppi, Disease, Taxonomy, LeLleBiplotData, PcaBiplotData, PpiFamily, CompoundTanimoto, create_tanimoto
25
from .ws import get_pdb_uniprot_mapping
26

Hervé  MENAGER's avatar
Hervé MENAGER committed
27

28
29
def index(request):
    return render(request, 'index.html')
30

Hervé  MENAGER's avatar
Hervé MENAGER committed
31

32
33
34
def about(request):
    return render(request, 'about.html')

Hervé  MENAGER's avatar
Hervé MENAGER committed
35

36
37
38
def general(request):
    return render(request, 'general.html')

Hervé  MENAGER's avatar
Hervé MENAGER committed
39

40
41
42
def tutorials(request):
    return render(request, 'tutorials.html')

43
44
def contribute(request):
    return render(request, 'contribute.html')
Hervé  MENAGER's avatar
Hervé MENAGER committed
45

46
47
48
def adminSession(request):
    return render(request, 'admin-session.html')

Rachel TORCHET's avatar
Rachel TORCHET committed
49
50
51
def update (request):
    return render(request, 'update.html')

52
53
#---------------------------------------------- CONTRIBUTE SESSION - WIZARD -------------------------------------------#

54
55
FORMS = [("IdForm", ippidb.forms.IdForm),
         ("BibliographyForm", ippidb.forms.BibliographyForm),
56
         ("PDBForm", ippidb.forms.PDBForm),
Hervé  MENAGER's avatar
Hervé MENAGER committed
57
58
         ("ProteinDomainComplexTypeForm",
          ippidb.forms.ProteinDomainComplexTypeForm),
59
         ("ProteinDomainComplexForm", ippidb.forms.ComplexCompositionFormSet),
Rachel TORCHET's avatar
Rachel TORCHET committed
60
         ("PpiForm", ippidb.forms.PpiAndComplexForm),
61
         ("CompoundForm", ippidb.forms.CompoundFormSet),
62
         ("TestsForm", ippidb.forms.TestsFormSet),]
63

64
65
TEMPLATES = {"IdForm": "IdForm.html",
             "BibliographyForm": "BibliographyForm.html",
66
             "PDBForm": "PDBForm.html",
Hervé  MENAGER's avatar
Hervé MENAGER committed
67
             "ProteinDomainComplexTypeForm": "ProteinDomainComplexTypeForm.html",
68
             "ProteinDomainComplexForm": "ProteinDomainComplexForm.html",
69
             "PpiForm": "PpiForm.html",
70
             "CompoundForm":"CompoundForm.html",
71
             "TestsForm":"TestsForm.html",
72
             }
73

Hervé  MENAGER's avatar
Hervé MENAGER committed
74

75
class IppiWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
76

77
78
    def get_context_data(self, **kwargs):
        context=super(IppiWizard, self).get_context_data(**kwargs)
79
80
81
82
        if self.steps.current == 'BibliographyForm':
            context['id_source']=self.storage.get_step_data('IdForm').get('IdForm-id_source')
            context['source']=self.storage.get_step_data('IdForm').get('IdForm-source')
            print(self.storage.get_step_data('IdForm').get('IdForm-id_source'))
83
        if self.steps.current == 'ProteinDomainComplexForm':
84
85
86
            context['complex_choice']=self.storage.get_step_data('ProteinDomainComplexTypeForm').get('ProteinDomainComplexTypeForm-complexChoice')
            context['complex_type']=self.storage.get_step_data('ProteinDomainComplexTypeForm').get('ProteinDomainComplexTypeForm-complexType')
            context['pdb']=self.storage.get_step_data('PDBForm').get('PDBForm-pdb_id')
87
        if self.steps.current == 'PpiForm':
88
89
            context['complex_choice']=self.storage.get_step_data('ProteinDomainComplexTypeForm').get('ProteinDomainComplexTypeForm-complexChoice')
            context['complex_type']=self.storage.get_step_data('ProteinDomainComplexTypeForm').get('ProteinDomainComplexTypeForm-complexType')
Rachel TORCHET's avatar
Rachel TORCHET committed
90
            context['pdb_id']=self.storage.get_step_data('PDBForm').get('PDBForm-pdb_id')
91
        return context
92

93
94
95
    def get_form_kwargs(self, step):
        # change args pass to a form
        kwargs = super().get_form_kwargs()
96
        if step == 'ProteinDomainComplexForm':
97
            pks = self.storage.get_step_data('PDBForm').get('pks')
98
99
100
101
102
103
104
105
            # print("test pks: ",pks)
            kwargs['form_kwargs']={'pks':pks}
        return kwargs

    def get_form_initial(self, step):
    # Works for form.forms
    # change value pass to a form
        initial = super().get_form_initial(step)
106
107
        if step == 'PpiForm':
            initial['pdb_id'] = self.storage.get_step_data('PDBForm').get('PDBForm-pdb_id')
108
        return initial
109

Rachel TORCHET's avatar
Rachel TORCHET committed
110
111
112
113
114
115
116
    def process_step(self, form):
        """
        This method overrides the one used to postprocess the form data.
        The added code just sets the form model for use in later forms
        when appropriate
        """
        data = super(IppiWizard, self).process_step(form).copy()
117
        if self.steps.current=='IdForm':
Rachel TORCHET's avatar
Rachel TORCHET committed
118
            form.instance.autofill()
119
        if self.steps.current in ['IdForm', 'Bibliography']:
Rachel TORCHET's avatar
Rachel TORCHET committed
120
121
122
            form.instance.save()
            data['pk'] = form.instance.id
        if self.steps.current == 'PDBForm':
123
            pdb_id = form.cleaned_data['pdb_id']
Rachel TORCHET's avatar
Rachel TORCHET committed
124
125
            uniprot_ids = []
            protein_ids = []
126
            uniprot_ids += get_pdb_uniprot_mapping(pdb_id)
Rachel TORCHET's avatar
Rachel TORCHET committed
127
128
129
130
131
132
133
134
135
136
137
            for uniprot_id in uniprot_ids:
                try:
                    p = Protein.objects.get(uniprot_id=uniprot_id)
                except Protein.DoesNotExist:
                    p = Protein()
                    p.uniprot_id = uniprot_ids[0]
                    p.autofill()
                    p.save()
                protein_ids.append(p.id)
            data['pks'] = protein_ids
        return data
138

Rachel TORCHET's avatar
Rachel TORCHET committed
139
    def get_form_instance(self, step):
140
    # Works only for Modelform
141
142
        if self.steps.current == 'BibliographyForm':
            pk = self.storage.get_step_data('IdForm').get('pk')
Rachel TORCHET's avatar
Rachel TORCHET committed
143
            return Bibliography.objects.get(pk=pk)
144
        if self.steps.current == 'ProteinDomainComplexTypeForm':
145
            print("blablabla", self.storage.get_step_data('PDBForm').get('pks'))
Rachel TORCHET's avatar
Rachel TORCHET committed
146
            pks = self.storage.get_step_data('PDBForm').get('pks')
147
            print("blablabla", self.storage.get_step_data('PDBForm').get('pks'))
Rachel TORCHET's avatar
Rachel TORCHET committed
148
149
            return Protein.objects.filter(id__in=pks)

150
151
    def get_template_names(self):
        return [TEMPLATES[self.steps.current]]
152

153
    def done(self, form_list, **kwargs):
154
155
156
157
        return render(self.request, 'done.html', {
        'form_data': [form.cleaned_data for form in form_list],
    })

158
#----------------------------------------------------------------------------------------------------------------------#
159

160

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
class CompoundListFilterHandler(object):

    def __init__(self, filter_class, relation_from_compound, relation_to_compound, parameter_name, filter_context, request_get):
        self.filter_class = filter_class
        self.relation_from_compound = relation_from_compound
        self.parameter_name = parameter_name
        self.filter_context = filter_context
        self.relation_from_compound = relation_from_compound
        self.relation_to_compound = relation_to_compound
        self.value = request_get.getlist(parameter_name)

    def process(self, queryset):
        """ to be called during queryset filtering """
        # if a value has been set for this filter
        if self.value:
            # filter queryset on the ID values specified
            queryset = queryset.filter(**{self.relation_from_compound + '__id__in': self.value})
            # selected objects on the ID values specified
            self.filter_context['selected_' + self.parameter_name] = self.filter_class.objects.filter(id__in = self.value)
            # unselected objects on the ID values not specified
            self.filter_context[self.parameter_name] = self.filter_class.objects.exclude(id__in = self.value)
        return queryset
    
    def post_process(self, compound_ids, queryset):
        """ to be called after queryset filtering """
        # if no value has been set for this filter
        if not self.value:
            # selected objects is an empty queryset
            self.filter_context['selected_' + self.parameter_name] = self.filter_class.objects.none()
            if compound_ids:
                # unselected objects is the range related to the current queryset
                self.filter_context[self.parameter_name] = self.filter_class.objects.filter(**{self.relation_to_compound + '__id__in':compound_ids}).distinct()
            else:
                # unselected objects are all objects if no filter is set
                self.filter_context[self.parameter_name] = self.filter_class.objects.all()

197
198
199
200
201
202
203
204
205
206
207
208
209
class CompoundSimilarityFilterHandler(object):

    def __init__(self, parameter_name, filter_context, request_get):
        self.parameter_name = parameter_name
        self.filter_context = filter_context
        if request_get.get(self.parameter_name):
            self.value = request_get.get(self.parameter_name).strip()
        else:
            self.value = None

    def process(self, queryset):
        """ to be called during queryset filtering """
        if self.value:
210
            fingerprint, query = self.value.split(':')
211
            self.filter_context[self.parameter_name] = self.value
212
213
            self.filter_context[self.parameter_name+'_fingerprint'] = fingerprint
            self.filter_context[self.parameter_name+'_query'] = query
214
            #FIXME: test if exists before running
215
            create_tanimoto(query, fingerprint)
216
            queryset = queryset.filter(compoundtanimoto__canonical_smiles=query, compoundtanimoto__fingerprint=fingerprint).annotate(tanimoto=F('compoundtanimoto__tanimoto'))
217
218
219
220
221
        return queryset
    
    def post_process(self, compound_ids, queryset):
        """ to be called after queryset filtering """
        pass
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
class TextSearchFilterHandler(object):

    def __init__(self, parameter_name, filter_context, request_get):
        self.parameter_name = parameter_name
        self.filter_context = filter_context
        if request_get.get(self.parameter_name):
            self.value = request_get.get(self.parameter_name).strip()
        else:
            self.value = None
        self.filter_context[self.parameter_name] = self.value

    def process(self, queryset):
        """ to be called during queryset filtering """
        if self.value:
            # filter on:
            # compound common name
            # PPI family name
            # bound complex protein short name
            # disease name
            queryset = queryset.filter(Q(common_name__icontains=self.value) |
                Q(compoundaction__ppi__family__name__icontains=self.value) |
                Q(compoundaction__ppi_id__ppicomplex__complex__protein__short_name__icontains=self.value) |
                Q(compoundaction__ppi__diseases__name__icontains=self.value) )
        return queryset
    
    def post_process(self, compound_ids, queryset):
        """ to be called after queryset filtering """
        pass

252
class CompoundRangeFilterHandler(object):
253

254
    def __init__(self, parameter_name, filter_context, request_get, step=1):
255
256
        self.parameter_name = parameter_name
        self.filter_context = filter_context
257
        self.value = None
258
        self.step = step or 1
259
260
261
262
263
264
265
        if request_get.get(parameter_name):
            self.value = request_get.get(parameter_name)
            self.value_min, self.value_max = self.value.split(',')
        else:
            self.value_min = None
            self.value_max = None

266
267
268
269
270
271
272

    def process(self, queryset):
        """ to be called during queryset filtering """
        # if a value has been set for this filter
        if self.value:
            # filter queryset on the ID values specified
            self.filter_context[self.parameter_name] = self.value
273
274
275
276
277
278
            self.filter_context[self.parameter_name + '_value_min'] = self.value_min
            self.filter_context[self.parameter_name + '_value_max'] = self.value_max
            filter_dict = {
                self.parameter_name + '__gte': self.value_min,
                self.parameter_name + '__lte': self.value_max,
                }
279
            queryset = queryset.filter(**filter_dict)
280
            # max and min value are the max and min for
281
            qs = Compound.objects
282
283
            self.filter_context[self.parameter_name+'_max'] = self.get_max(qs)
            self.filter_context[self.parameter_name+'_min'] = self.get_min(qs)
284
285
        return queryset

286
287
288
289
290
291
292
293
294
295
296
    def get_max(self, qs):
        val = float(qs.aggregate(Max(self.parameter_name))[self.parameter_name + '__max'] or 0)
        val = val - val % self.step + self.step
        return str(val)

    def get_min(self, qs):
        val = float(qs.aggregate(Min(self.parameter_name))[self.parameter_name + '__min'] or 0)
        val = val - val % self.step + self.step
        return str(val)


297
298
299
300
    def post_process(self, compound_ids, queryset):
        """ to be called after queryset filtering """
        # if no value has been set for this filter
        if not self.value:
301
302
            self.filter_context[self.parameter_name+'_max'] = self.get_max(queryset)
            self.filter_context[self.parameter_name+'_min'] = self.get_min(queryset)
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324

class ExistsFilterHandler(object):

    def __init__(self, parameter_name, filter_context, request_get):
        self.parameter_name = parameter_name
        self.filter_context = filter_context
        self.value = None
        if request_get.get(parameter_name):
            self.value = request_get.get(parameter_name)
            self.filter_context[self.parameter_name] = self.value

    def process(self, queryset):
        """ to be called during queryset filtering """
        # if a value has been set for this filter
        if self.value:
            # filter queryset on the ID values specified
            queryset = queryset.exclude(**{self.parameter_name+'__isnull':not(self.value)})
        return queryset

    def post_process(self, compound_ids, queryset):
        """ to be called after queryset filtering """
        pass
325
326
327
328
329
330
331

class CompoundListView(ListView):

    model = Compound
    template_name = "compound_list.html"
    paginate_by = 15
    
Hervé  MENAGER's avatar
Hervé MENAGER committed
332
    table_view_default_fields = ['id', 'canonical_smiles', 'common_name', 'molecular_weight', 'a_log_p', 'compound_action_ligand_ids', 'pubs']
333

334
    sort_by_option_ids = ['id', 'molecular_weight', 'a_log_p', 'nb_aromatic_sssr', 'nb_chiral_centers', 'pubs', 'le', 'lle', 'best_activity']
335
336

    def get_ordering(self):
337
338
339
340
341
342
        # sort by options
        self.sort_by_options = OrderedDict()
        compound_fields = {f.name: f.verbose_name for f in Compound._meta.get_fields() if not(f.is_relation)}
        for sort_by_option_id in self.sort_by_option_ids:
            if sort_by_option_id == 'pubs':
                name = 'Number of publications'
343
            elif sort_by_option_id == 'le':
344
                name = 'Ligand Efficiency'
345
            elif sort_by_option_id == 'lle':
346
                name = 'Lipophilic Efficiency'
347
            elif sort_by_option_id == 'best_activity':
348
                name = 'Best Activity'
349
350
351
352
            else:
                name = compound_fields.get(sort_by_option_id)
            self.sort_by_options[sort_by_option_id] = {'name': name, 'order':'ascending', 'id': sort_by_option_id}
            self.sort_by_options['-'+sort_by_option_id] = {'name': name, 'order':'descending', 'id': sort_by_option_id}
353
        if 'similar_to' in self.request.GET:
354
355
356
357
358
359
360
            self.sort_by_options['tanimoto'] = {'name': 'Tanimoto similarity', 'order':'ascending', 'id': 'tanimoto'}
            self.sort_by_options['-tanimoto'] = {'name': 'Tanimoto similarity', 'order':'descending', 'id': 'tanimoto'}
        default_sort = '-tanimoto' if 'similar_to' in self.request.GET else 'id'
        self.sort_by = self.request.GET.get('sort_by', default_sort)
        if self.sort_by not in self.sort_by_options.keys():
            self.sort_by = 'id'
        return self.sort_by
361
362
363
364
365
366
367
368
369

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # process display configuration
        display = self.request.GET.get('display')
        if display not in ['l', 't']:
            display = 'v'
        context['display']=display
        # displayed fields in "table" mode
370
        fields = self.request.GET.getlist('fields', self.table_view_default_fields.copy())
371
372
        if 'id' not in fields:
            fields.append('id')
373
374
        if 'similar_to' in self.request.GET:
            fields.append('tanimoto')
375
376
377
        context['fields'] = fields
        # filters
        context.update(self.filter_context)
378
379
        context['sort_by'] = self.sort_by
        context['sort_by_options'] = self.sort_by_options
380
381
382
383
384
385
386
387
388
389
390
391
392
393
        # return context
        return context

    def get_queryset(self):
        self.filter_context = {}
        # get queryset
        qs = super().get_queryset()
        # add filters
        cfhs = [\
            CompoundListFilterHandler(PpiFamily, 'compoundaction__ppi__family', 'ppi__compoundaction__compound', 'family', self.filter_context, self.request.GET),
            CompoundListFilterHandler(Ppi, 'compoundaction__ppi', 'compoundaction__compound', 'ppi', self.filter_context, self.request.GET),
            CompoundListFilterHandler(Disease, 'compoundaction__ppi__diseases', 'ppi__compoundaction__compound', 'disease', self.filter_context, self.request.GET),
            CompoundListFilterHandler(Taxonomy, 'compoundaction__ppi_id__ppicomplex__complex__protein__organism', 'protein__proteindomaincomplex__ppicomplex__ppi__compoundaction__compound', 'taxonomy', self.filter_context, self.request.GET),
            CompoundListFilterHandler(ProteinDomainBoundComplex, 'compoundaction__ppi_id__ppicomplex__complex', 'ppicomplex__ppi__compoundaction__compound', 'boundcomplex', self.filter_context, self.request.GET),
394
            CompoundRangeFilterHandler('molecular_weight', self.filter_context, self.request.GET, 50),
395
396
            CompoundRangeFilterHandler('a_log_p', self.filter_context, self.request.GET),
            CompoundRangeFilterHandler('nb_donor_h', self.filter_context, self.request.GET),
397
            CompoundRangeFilterHandler('nb_acceptor_h', self.filter_context, self.request.GET),
398
            CompoundRangeFilterHandler('tpsa', self.filter_context, self.request.GET, 50),
399
            CompoundRangeFilterHandler('nb_rotatable_bonds', self.filter_context, self.request.GET),
400
            CompoundRangeFilterHandler('nb_aromatic_sssr', self.filter_context, self.request.GET),
401
            CompoundRangeFilterHandler('nb_chiral_centers', self.filter_context, self.request.GET),
402
            CompoundRangeFilterHandler('fsp3', self.filter_context, self.request.GET, 0.1),
403
            CompoundRangeFilterHandler('pubs', self.filter_context, self.request.GET),
404
405
406
            CompoundRangeFilterHandler('best_activity', self.filter_context, self.request.GET),
            CompoundRangeFilterHandler('le', self.filter_context, self.request.GET, 0.1),
            CompoundRangeFilterHandler('lle', self.filter_context, self.request.GET),
407
408
409
            ExistsFilterHandler('pubchem_id', self.filter_context, self.request.GET),
            ExistsFilterHandler('chemspider_id', self.filter_context, self.request.GET),
            ExistsFilterHandler('chembl_id', self.filter_context, self.request.GET),
410
            CompoundSimilarityFilterHandler('similar_to', self.filter_context, self.request.GET),
411
            TextSearchFilterHandler('q', self.filter_context, self.request.GET),
412
413
414
415
416
417
418
        ]
        for cfh in cfhs:
            qs = cfh.process(qs)
        # post filter filters
        if self.filter_context:
            # compound ids in the final selection if a selection has happened
            cids = [c for c in qs.all().values_list('id', flat=True)]
419
        else:
420
421
422
423
424
425
            cids = None
        for cfh in cfhs:
            cfh.post_process(cids, qs)
        # return queryset
        return qs

426
427
428
429
430
431

def compound_card(request, compound_id):
    try:
        compound = Compound.objects.get(id=int(compound_id))
    except Compound.DoesNotExist:
        raise Http404("No compound data for %s:%s" % (compound_id))
432
    le_lle_biplot_data = LeLleBiplotData.objects.get().le_lle_biplot_data
433
434
435
    pca_biplot = json.loads(PcaBiplotData.objects.get().pca_biplot_data)
    pca_biplot_data = json.dumps(pca_biplot['data'])
    pca_biplot_cc = pca_biplot['correlation_circle']
436
437
438
439
440
441
    return render(request, 'compound_card.html', {'compound': compound, 'le_lle_biplot_data': le_lle_biplot_data, 'pca_biplot_data': pca_biplot_data, 'pca_biplot_cc': pca_biplot_cc})

class CompoundCardBDCHEMRedirectView(RedirectView):

    def get_redirect_url(self, *args, **kwargs):
        kwargs['compound_id'] = str(int(kwargs['compound_id']))
442
443
444
445
446
447
448
        return reverse('compound_card', kwargs=kwargs)

def convert_mol2smi(request):
    mol_string = request.GET.get('molString')
    smiles_string = mol2smi(mol_string)
    resp = {'smiles': smiles_string}
    return JsonResponse(resp)
449
450
451
452
453
454

def convert_smi2mol(request):
    smi_string = request.GET.get('smiString')
    mol_string = smi2mol(smi_string)
    resp = {'mol': mol_string}
    return JsonResponse(resp)