Commit 6f5a4bb9 authored by Hervé  MENAGER's avatar Hervé MENAGER
Browse files

restructure the compounds list view

* this view is now class based and a lot more structured
* filters are handled supposedly in a clever manner


Former-commit-id: 510e9b5282b06d28fb839ba20ff44d76ab41f584
parent 85b91008
......@@ -30,32 +30,32 @@
{% include "slider_button.html" with label="Publications" param_name="pubs" %}
</div>
<div class="m-2 d-flex ">
<span>{{ compounds.paginator.count }} compounds</span>
{% if selected_families or selected_ppis or selected_diseases or selected_taxonomies or selected_boundcomplexes or molecular_weight != molecular_weight_max or a_log_p != a_log_p_max or nb_donor_h != nb_donor_h_max or pubs != pubs_min %}
<span>{{ paginator.count }} compounds</span>
{% if selected_family or selected_ppi or selected_disease or selected_taxonomy or selected_boundcomplex or molecular_weight != molecular_weight_max or a_log_p != a_log_p_max or nb_donor_h != nb_donor_h_max or pubs != pubs_min %}
<span>
&nbsp;-&nbsp;filters:&nbsp;
{% if selected_families %}
{% for selected in selected_families %}
{% if selected_family %}
{% for selected in selected_family %}
{% include "selected_badge.html" with param_name="family" selected=selected %}
{% endfor %}
{% endif %}
{% if selected_ppis %}
{% for selected in selected_ppis %}
{% if selected_ppi %}
{% for selected in selected_ppi %}
{% include "selected_badge.html" with param_name="ppi" selected=selected %}
{% endfor %}
{% endif %}
{% if selected_diseases %}
{% for selected in selected_diseases %}
{% if selected_disease %}
{% for selected in selected_disease %}
{% include "selected_badge.html" with param_name="disease" selected=selected %}
{% endfor %}
{% endif %}
{% if selected_taxonomies %}
{% for selected in selected_taxonomies %}
{% if selected_taxonomy %}
{% for selected in selected_taxonomy %}
{% include "selected_badge.html" with param_name="taxonomy" selected=selected %}
{% endfor %}
{% endif %}
{% if selected_boundcomplexes %}
{% for selected in selected_boundcomplexes %}
{% if selected_boundcomplex %}
{% for selected in selected_boundcomplex %}
{% include "selected_badge.html" with param_name="boundcomplex" selected=selected %}
{% endfor %}
{% endif %}
......@@ -96,20 +96,20 @@
</span>
</div>
</form>
{% if compounds %}
{% if page_obj %}
<div class="container-fluid">
{% if display == 'v' %}
<div class="d-flex flex-wrap m-2 justify-content-around">
{% for compound in compounds %}
{% for compound in page_obj %}
{% include "compound_v_item.html" with compound=compound %}
{% endfor %}
</div>
{% elif display == 'l'%}
{% for compound in compounds %}
{% for compound in page_obj %}
{% include "compound_l_item.html" with compound=compound %}
{% endfor %}
{% else %}
{% include "compound_t_list.html" with compounds=compounds %}
{% include "compound_t_list.html" with compounds=page_obj %}
{% endif %}
</div>
{% else %}
......@@ -121,9 +121,9 @@
<nav class="d-flex">
<ul class="pagination pagination-sm ml-auto">
{% if compounds.has_previous %}
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?{% url_replace request 'page' compounds.previous_page_number %}" aria-label="Previous">
<a class="page-link" href="?{% url_replace request 'page' page_obj.previous_page_number %}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
<span class="sr-only">Previous</span>
</a>
......@@ -131,12 +131,12 @@
{% endif %}
<li class="page-item disabled">
<span class="page-link">Page {{ compounds.number }} of {{ compounds.paginator.num_pages }}.</span>
<span class="page-link">Page {{ page_obj.number }} of {{ paginator.num_pages }}.</span>
</li>
{% if compounds.has_next %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{% url_replace request 'page' compounds.next_page_number %}" aria-label="Next">
<a class="page-link" href="?{% url_replace request 'page' page_obj.next_page_number %}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
<span class="sr-only">Next</span>
</a>
......@@ -145,7 +145,7 @@
<li class="page-item ml-3">
<div class="input-group input-group-sm">
<input id="pageNumber" type="number" min="1" max="{{compounds.paginator.num_pages}}" step="1" placeholder="page #" style="width: 4em;" aria-label="Page number">
<input id="pageNumber" type="number" min="1" max="{{ paginator.num_pages }}" step="1" placeholder="page #" style="width: 4em;" aria-label="Page number">
<div class="input-group-append">
<button class="btn btn-outline-primary" type="button" onclick="modifyUrl('page',$('#pageNumber').val())">Go</button>
</div>
......@@ -155,11 +155,11 @@
</ul>
</nav>
{% include "multiselection_modal.html" with label="PPI Family" selected_list=selected_families unselected_list=families param_name="family" all_param_name="families_all" all_param_value=families_all %}
{% include "multiselection_modal.html" with label="PPI" selected_list=selected_ppis unselected_list=ppis param_name="ppi" all_param_name="ppis_all" all_param_value=ppis_all %}
{% include "multiselection_modal.html" with label="Disease" selected_list=selected_diseases unselected_list=diseases param_name="disease" all_param_name="diseases_all" all_param_value=diseases_all %}
{% include "multiselection_modal.html" with label="Organism" selected_list=selected_taxonomies unselected_list=taxonomies param_name="taxonomy" all_param_name="taxonomies_all" all_param_value=taxonomies_all %}
{% include "multiselection_modal.html" with label="Bound complex" selected_list=selected_boundcomplexes unselected_list=boundcomplexes param_name="boundcomplex" all_param_name="boundcomplexes_all" all_param_value=boundcomplexes_all %}
{% include "multiselection_modal.html" with label="PPI Family" selected_list=selected_family unselected_list=family param_name="family" all_param_name="family_all" all_param_value=family_all %}
{% include "multiselection_modal.html" with label="PPI" selected_list=selected_ppi unselected_list=ppi param_name="ppi" all_param_name="ppi_all" all_param_value=ppi_all %}
{% include "multiselection_modal.html" with label="Disease" selected_list=selected_disease unselected_list=disease param_name="disease" all_param_name="disease_all" all_param_value=disease_all %}
{% include "multiselection_modal.html" with label="Organism" selected_list=selected_taxonomy unselected_list=taxonomy param_name="taxonomy" all_param_name="taxonomy_all" all_param_value=taxonomy_all %}
{% include "multiselection_modal.html" with label="Bound complex" selected_list=selected_boundcomplex unselected_list=boundcomplex param_name="boundcomplex" all_param_name="boundcomplex_all" all_param_value=boundcomplex_all %}
{% include "slider_modal.html" with label="Molecular Weight" param_name="molecular_weight" param_min=molecular_weight_min param_max=molecular_weight_max param_value=molecular_weight step='50' param_label='Select a cutoff value for the molecular weight of the compounds to be selected'%}
{% include "slider_modal.html" with label="AlogP" param_name="a_log_p" param_min=a_log_p_min param_max=a_log_p_max param_value=a_log_p step='1' param_label='Select a cutoff value for the AlogP of the compounds to be selected'%}
{% include "slider_modal.html" with label="H donors" param_name="nb_donor_h" param_min=nb_donor_h_min param_max=nb_donor_h_max param_value=nb_donor_h step='1' param_label='Select a cutoff value for the number of H donors in the compounds to be selected'%}
......
......@@ -11,7 +11,7 @@ urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^about$', views.about, name='about'),
url(r'^about/general/$', views.general, name='general'),
url(r'^compounds/$', views.compound_list, name='compound_list'),
url(r'^compounds/$', views.CompoundListView.as_view(), name='compound_listbeta'),
url(r'^compounds/(?P<compound_id>\w+)$', views.compound_card, name='compound_card'),
url(r'^tutorials$', views.tutorials, name='tutorials'),
url(r'^admin-session$', views.adminSession, name='admin-session'),
......
......@@ -7,6 +7,7 @@ from django.shortcuts import render
from django.http import HttpResponseRedirect, Http404
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.list import ListView
from formtools.wizard.views import SessionWizardView, NamedUrlSessionWizardView
import ippidb
......@@ -143,99 +144,151 @@ class IppiWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
'form_data': [form.cleaned_data for form in form_list],
})
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()
class CompoundCutoffFilterHandler(object):
def __init__(self, parameter_name, cutoff_dir, filter_context, request_get):
self.parameter_name = parameter_name
self.cutoff_dir = cutoff_dir
self.filter_context = filter_context
self.value = request_get.get(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
self.filter_context[self.parameter_name] = self.value
filter_dict = {self.parameter_name + '__' + self.cutoff_dir + 'te': self.value}
queryset = queryset.filter(**filter_dict)
# max and min value are the max and min for
self.filter_context[self.parameter_name+'_max'] = str(int(math.ceil(float(Compound.objects.aggregate(Max(self.parameter_name))[self.parameter_name + '__max'] or 0))))
self.filter_context[self.parameter_name+'_min'] = str(int(math.floor(float(Compound.objects.aggregate(Min(self.parameter_name))[self.parameter_name + '__min'] or 0))))
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:
self.filter_context[self.parameter_name+'_max'] = str(int(math.ceil(float(queryset.aggregate(Max(self.parameter_name))[self.parameter_name + '__max'] or 0))))
self.filter_context[self.parameter_name+'_min'] = str(int(math.floor(float(queryset.aggregate(Min(self.parameter_name))[self.parameter_name + '__min'] or 0))))
if self.cutoff_dir == 'l':
self.filter_context[self.parameter_name] = self.filter_context[self.parameter_name+'_max']
else:
self.filter_context[self.parameter_name] = self.filter_context[self.parameter_name+'_min']
class CompoundListView(ListView):
model = Compound
template_name = "compound_list.html"
paginate_by = 15
table_view_default_fields = ['id', 'common_name', 'molecular_weight', 'a_log_p', 'compound_action_pdb_ids', 'pubs']
def process_cutoff_value(name, context, request, cutoff_dir='l'):
c = context['compounds']
context[name+'_max'] = str(int(math.ceil(float(c.aggregate(Max(name))[name + '__max'] or 0))))
context[name+'_min'] = str(int(math.floor(float(c.aggregate(Min(name))[name + '__min'] or 0))))
if request.GET.get(name):
context[name] = request.GET.get(name)
filter_dict = {name + '__' + cutoff_dir + 'te': context[name]}
context['compounds'] = context['compounds'].filter(**filter_dict)
elif cutoff_dir == 'l':
context[name] = context[name+'_max']
else:
context[name] = context[name+'_min']
return context
def compound_list(request):
"""
Display the list of compounds
"""
context = {}
# pubs is the number of publications referring to the compound
compounds = Compound.objects.annotate(pubs=Count('refcompoundbiblio'))
context['compounds'] = compounds
if request.GET.get('family'):
context['compounds'] = context['compounds'].filter(compoundaction__ppi__family__id__in=request.GET.getlist('family'))
if request.GET.get('ppi'):
context['compounds'] = context['compounds'].filter(compoundaction__ppi__id__in=request.GET.getlist('ppi'))
if request.GET.get('disease'):
context['compounds'] = context['compounds'].filter(compoundaction__ppi__diseases__id__in=request.GET.getlist('disease'))
if request.GET.get('taxonomy'):
context['compounds'] = context['compounds'].filter(compoundaction__ppi_id__ppicomplex__complex__protein__organism__id__in=request.GET.getlist('taxonomy'))
if request.GET.get('boundcomplex'):
context['compounds'] = context['compounds'].filter(compoundaction__ppi_id__ppicomplex__complex__id__in=request.GET.getlist('boundcomplex'))
context = process_cutoff_value('molecular_weight', context, request)
context = process_cutoff_value('a_log_p', context, request)
context = process_cutoff_value('nb_donor_h', context, request)
context = process_cutoff_value('pubs', context, request, 'g')
selected_families = PpiFamily.objects.filter(id__in=request.GET.getlist('family'))
families = PpiFamily.objects.exclude(id__in=request.GET.getlist('family'))
selected_ppis = Ppi.objects.filter(id__in=request.GET.getlist('ppi'))
ppis = Ppi.objects.exclude(id__in=request.GET.getlist('ppi'))
selected_diseases = Disease.objects.filter(id__in=request.GET.getlist('disease'))
diseases = Disease.objects.exclude(id__in=request.GET.getlist('disease'))
selected_taxonomies = Taxonomy.objects.filter(id__in=request.GET.getlist('taxonomy'))
taxonomies = Taxonomy.objects.exclude(id__in=request.GET.getlist('taxonomy'))
selected_boundcomplexes = ProteinDomainBoundComplex.objects.filter(id__in=request.GET.getlist('boundcomplex'))
boundcomplexes = ProteinDomainBoundComplex.objects.exclude(id__in=request.GET.getlist('boundcomplex'))
sort_by_option_ids = ['id', 'molecular_weight', 'a_log_p', 'nb_aromatic_sssr', 'nb_chiral_centers', 'pubs']
sort_by = request.GET.get('sort_by', 'id')
context['compounds'] = context['compounds'].order_by(sort_by)
context['compounds'] = context['compounds'].distinct()
# handle pagination in compounds list
paginator = Paginator(context['compounds'], 15)
page = request.GET.get('page')
try:
context['compounds'] = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
context['compounds'] = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
context['compounds'] = paginator.page(paginator.num_pages)
display = request.GET.get('display')
if display not in ['l', 't']:
display = 'v'
context['display']=display
compound_fields = {f.name: f.verbose_name for f in Compound._meta.get_fields() if not(f.is_relation)}
sort_by_options = OrderedDict()
for sort_by_option_id in sort_by_option_ids:
if sort_by_option_id == 'pubs':
name = 'Number of publications'
def get_ordering(self):
sort_by = self.request.GET.get('sort_by', 'id')
return sort_by
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
fields = self.request.GET.getlist('fields', self.table_view_default_fields)
if 'id' not in fields:
fields.append('id')
context['fields'] = fields
# filters
context.update(self.filter_context)
# sort by options
compound_fields = {f.name: f.verbose_name for f in Compound._meta.get_fields() if not(f.is_relation)}
sort_by_options = OrderedDict()
for sort_by_option_id in self.sort_by_option_ids:
if sort_by_option_id == 'pubs':
name = 'Number of publications'
else:
name = compound_fields.get(sort_by_option_id)
sort_by_options[sort_by_option_id] = {'name': name, 'order':'ascending', 'id': sort_by_option_id}
sort_by_options['-'+sort_by_option_id] = {'name': name, 'order':'descending', 'id': sort_by_option_id}
context['sort_by'] = self.get_ordering()
context['sort_by_options'] = sort_by_options
# return context
return context
def get_queryset(self):
self.filter_context = {}
# get queryset
qs = super().get_queryset()
# annotate
qs = qs.annotate(pubs=Count('refcompoundbiblio'))
# 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),
CompoundCutoffFilterHandler('molecular_weight', 'l', self.filter_context, self.request.GET),
CompoundCutoffFilterHandler('a_log_p', 'l', self.filter_context, self.request.GET),
CompoundCutoffFilterHandler('nb_donor_h', 'l', self.filter_context, self.request.GET),
CompoundCutoffFilterHandler('pubs', 'g', self.filter_context, self.request.GET),
]
for cfh in cfhs:
qs = cfh.process(qs)
# sort results
# 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)]
else:
name = compound_fields.get(sort_by_option_id)
sort_by_options[sort_by_option_id] = {'name': name, 'order':'ascending', 'id': sort_by_option_id}
sort_by_options['-'+sort_by_option_id] = {'name': name, 'order':'descending', 'id': sort_by_option_id}
fields = request.GET.getlist('fields',['id', 'common_name', 'molecular_weight', 'a_log_p', 'compound_action_pdb_ids', 'pubs'])
if 'id' not in fields:
fields.append('id')
context['selected_ppis'] = selected_ppis
context['ppis'] = ppis
context['selected_families'] = selected_families
context['families'] = families
context['selected_diseases'] = selected_diseases
context['diseases'] = diseases
context['selected_taxonomies'] = selected_taxonomies
context['taxonomies'] = taxonomies
context['selected_boundcomplexes'] = selected_boundcomplexes
context['boundcomplexes'] = boundcomplexes
context['fields'] = fields
context['sort_by'] = sort_by
context['sort_by_options'] = sort_by_options
return render(request, 'compound_list.html', context)
cids = None
for cfh in cfhs:
cfh.post_process(cids, qs)
# return queryset
return qs
def compound_card(request, compound_id):
try:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment