Commit 0c159fe3 authored by Hervé  MENAGER's avatar Hervé MENAGER
Browse files

Chemical similarity part 1 (WIP)

Add code to embed MarvinJS editor and convert mol to smiles
TODO: add ansible code to install MarvinJS code and license
TODO: add ansible code to install openbabel and pybel

Former-commit-id: 04e76c75250ddff1fa45ddf4f4f9277995ae75a4
parent a9a774ef
<!DOCTYPE html>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<base href="/static/marvinjs-18/" />
<title>Marvin JS</title>
<link type="text/css" rel="stylesheet" href="gui/css/editor.min.css" media="screen" />
<script src="gui/lib/promise-1.0.0.min.js"></script>
<script src="gui/gui.nocache.js"></script>
window.addEventListener("message", function(event) {
try {
var externalCall = JSON.parse(;
marvin.onReady(function() {
marvin.sketcherInstance[externalCall.method].apply(marvin.sketcherInstance, externalCall.args);
} catch (e) {
}, false);
// called when Marvin JS loaded
function sketchOnLoad () {
if(marvin.Sketch.isSupported()) {
marvin.sketcherInstance = new marvin.Sketch("sketch");
} else {
alert("Cannot initiate sketcher. Current browser may not support HTML5 canvas or may run in Compatibility Mode.");
<body class="mjs-body">
<p>Your web browser must have JavaScript enabled in order for this application to display correctly.</p>
<div id="sketch"></div>
......@@ -3,6 +3,12 @@
{% block title %}compounds list{% endblock %}
{% block extra_js %}
<script src="/static/marvinjs-18/js/util.js"></script>
<script src="/static/marvinjs-18/js/marvinjslauncher.js"></script>
<script src="/static/marvinjs-18/gui/lib/promise-1.0.0.min.js"></script>
{% endblock %}
{% block content %}
<div class="inner-wrap">
<nav class="breadcrumb breadNav" role="navigation">
......@@ -28,10 +34,11 @@
{% include "slider_button.html" with label="AlogP" param_name="a_log_p" %}
{% include "slider_button.html" with label="H donors" param_name="nb_donor_h" %}
{% include "slider_button.html" with label="Publications" param_name="pubs" %}
{% include "marvinjs_button.html" %}
<div class="m-2 d-flex ">
<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 %}
{% 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 or similar_to %}
{% if selected_family %}
......@@ -71,6 +78,9 @@
{% if pubs != pubs_min %}
{% include "slider_badge.html" with param_name="pubs" param_value=pubs param_label="Publications" %}
{% endif %}
{% if similar_to %}
{% include "similarity_badge.html" with param_name="similar_to" param_value=similar_to param_label="Similar to" %}
{% endif %}
{% endif %}
<div class="btn-group m-1 ml-auto">
......@@ -164,5 +174,6 @@
{% 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'%}
{% include "slider_modal.html" with label="Publications" param_name="pubs" param_min=pubs_min param_max=pubs_max param_value=pubs step='1' param_label='Select a cutoff value for the number of publications mentioning the compounds to be selected'%}
{% include "marvinjs_modal.html" %}
{% endblock %}
<button type="button" class="btn btn-primary btn-lg m-1" style="width: inherit!important" data-toggle="modal" data-target="#modal-marvin">
<i class="fa fa-filter mr-2"></i> Chemical similarity
<div class="modal fade" id="modal-marvin" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Query structure</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
<div class="modal-body">
Sketch or upload (in .mol format and in 2D only) the query structure here
<iframe id="marviniframe" src="/static/marvin-ippidbeditor.html" style="overflow: hidden; min-width: 700px; min-height: 500px; border: 1px solid darkgray;" />
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button id="marvinApplyButton" type="button" class="btn btn-primary">Apply</button>
document.getElementById("marviniframe").addEventListener("load", function(){
var marvinSketcherInstance;
MarvinJSUtil.getEditor("marviniframe").then(function(sketcherInstance) {
marvinSketcherInstance = sketcherInstance;
marvinSketcherInstance.exportStructure("mol").then(function(source) {
url: '/utils/mol2smi',
data: {'molString':source},
success: function(data){
}, function(error) {
alert("Molecule export failed: "+error);
}, function(error) {
alert("Loading of the sketcher failed"+error);
<span class="badge badge-info" style="font-size: 100%" title="{{ param_value }}">{{ param_label }}: {% include "compound_smiles_draw.html" with id='similar_to' smile=param_value width='30' height='16' theme="dark"%}
<a href="#" style="color: white;" onclick="removeFromSelection('{{ param_name }}', '{{ param_value }}')"><i class="remove fa fa-times-circle"></i></a>
......@@ -21,6 +21,7 @@ urlpatterns = [
ippidb_wizard, name='ippidb_step'),
url(r'^admin-session/add/$', ippidb_wizard, name='ippidb'),
url(r'^admin-session/update/$', views.update, name='update'),
url(r'^utils/mol2smi$', views.convert_mol2smi, name='mol2smi'),
from django.conf import settings
#FIXME: to work, this currently needs awkward PYTHONPATH tweaks, like:
#$export PYTHONPATH=/home/hmenager/openbabellocal/lib/python3.6/site-packages/:$PYTHONPATH
import pybel
def mol2smi(mol_string):
m = pybel.readstring('mol', mol_string)
return m.write(format='smi')
......@@ -5,7 +5,7 @@ from collections import OrderedDict
from django.db.models import Max, Min, Count
from django.shortcuts import render
from django.urls import reverse
from django.http import HttpResponseRedirect, Http404
from django.http import HttpResponseRedirect, Http404, JsonResponse
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.list import ListView
......@@ -14,6 +14,7 @@ from formtools.wizard.views import SessionWizardView, NamedUrlSessionWizardView
import ippidb
from .forms import *
from .utils import mol2smi
from .models import Protein, Bibliography, ProteinDomainComplex, ProteinDomainBoundComplex, RefCompoundBiblio, TestActivityDescription, Compound, Ppi, Disease, Taxonomy, LeLleBiplotData, PcaBiplotData, PpiFamily
from .ws import get_pdb_uniprot_mapping
......@@ -187,6 +188,25 @@ class CompoundListFilterHandler(object):
# unselected objects are all objects if no filter is set
self.filter_context[self.parameter_name] = self.filter_class.objects.all()
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()
self.value = None
def process(self, queryset):
""" to be called during queryset filtering """
if self.value:
self.filter_context[self.parameter_name] = self.value
return queryset
def post_process(self, compound_ids, queryset):
""" to be called after queryset filtering """
class CompoundCutoffFilterHandler(object):
......@@ -284,6 +304,7 @@ class CompoundListView(ListView):
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),
CompoundSimilarityFilterHandler('similar_to', self.filter_context, self.request.GET),
for cfh in cfhs:
qs = cfh.process(qs)
......@@ -315,4 +336,10 @@ class CompoundCardBDCHEMRedirectView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
kwargs['compound_id'] = str(int(kwargs['compound_id']))
return reverse('compound_card', kwargs=kwargs)
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)
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