Commit 314c074a authored by Bryan  BRANCOTTE's avatar Bryan BRANCOTTE
Browse files

new CompoundFormSet, now linked to marvin js, validate that either smile or...

new CompoundFormSet, now linked to marvin js, validate that either smile or iupac are provided but not both, and that compound_name (in pub name) is unique for the current publication
parent 64e71fe7
Pipeline #9392 passed with stage
in 10 minutes and 58 seconds
......@@ -208,36 +208,68 @@ TYPE_MOLECULE = (
('iupac', 'iupac'),
('sketch','sketch'),
)
class CompoundForm(ModelForm):
molecule_comp = forms.CharField(
label="Molecule composition",
required=True,
)
compound_name = forms.CharField(
label=_("compound_name_label"),
required=True,
help_text=_("compound_name_help_text"),
)
molecule = forms.CharField(
label="Molecule",
required=True,
widget=forms.RadioSelect(choices=TYPE_MOLECULE),
molecule_smiles = forms.CharField(
label=_("molecule_smiles_label"),
help_text=_("molecule_smiles_help_text"),
required=False,
widget=forms.Textarea(attrs={"class": "molecule-composition", 'rows': '1'}),
)
molecule_iupac = forms.CharField(
label=_("_molecule_iupac_label"),
help_text=_("molecule_iupac_help_text"),
required=False,
widget=forms.Textarea(attrs={"class": "molecule-composition", 'rows': '1'}),
)
class Meta:
model = Compound
fields = ['common_name', 'is_macrocycle']
widgets = {
'common_name': forms.TextInput(attrs={'required': 'required'}),
}
def full_clean(self):
super().full_clean()
if not self.is_bound: # Stop further processing.
return
if self.cleaned_data.get("molecule_smiles", "") == "" \
and self.cleaned_data.get("molecule_iupac", "") == "" \
or self.cleaned_data.get("molecule_smiles", "") != "" \
and self.cleaned_data.get("molecule_iupac", "") != "":
self.add_error("molecule_smiles", "You have to provide either its IUPAC or its smiles, but not both")
self.add_error("molecule_iupac", "You have to provide either its IUPAC or its smiles, but not both")
class RefCompoundBiblioForm(ModelForm):
model = RefCompoundBiblio
exclude = ['compound', 'bibliography']
CompoundFormSet = formset_factory(CompoundForm, can_delete=True, extra=1)
class CompoundFormFormSet(forms.BaseFormSet):
def clean(self):
"""Checks that no two articles have the same title."""
if any(self.errors):
# Don't bother validating the formset unless each form is valid on its own
return
compound_names = set()
for form in self.forms:
if form.cleaned_data.get("DELETE", False):
continue
compound_name = form.cleaned_data['compound_name']
if compound_name in compound_names:
raise forms.ValidationError(
_("Compound must have distinct compound_name_label as (explanation). Incriminated value:'%s'") %
compound_name
)
compound_names.add(compound_name)
CompoundFormSet = formset_factory(
form=CompoundForm,
formset=CompoundFormFormSet,
can_delete=True,
extra=1,
)
""" Step 8 : TestsForm """
......
......@@ -201,7 +201,14 @@ Description: IPPI-DB Theme
text-align: left;
padding-bottom: 1%;
}
.smiles-inpout-and-edit{
display:flex;
}
.smiles-inpout-and-edit>*:first-child{
flex:1 auto;
margin-right:0.5em;
}
.compound .form-check,
.compound .input_field input {
text-align: left;
}
......@@ -1337,3 +1344,9 @@ Description: IPPI-DB Theme
transform: translate3d(0, -100%, 0);
opacity: 1;
}
.form-check>small.form-text.text-muted {
margin-top: -0.25em;
margin-left: -1.25rem;
margin-bottom: 0.45em;
}
\ No newline at end of file
function update_molecule_composition_fields(){
let elt_id = $(this).attr('id');
let that = $(this).closest(".compound").find(".molecule-composition:not('#"+elt_id+"')");
if($(this).val()==''){
if (that.val()==''){
$(this).add(that).prop("disabled",false);
}else{
$(this).prop("disabled",true);
}
}else{
that.val('').prop("disabled",true);
}
}
function showModalFromMe(src){
$("#marviniframe").attr("data-smiles",$(src).closest(".smiles-inpout-and-edit").find(".molecule-composition").val());
$("#marviniframe").attr("data-smile-target",$(src).closest(".smiles-inpout-and-edit").find(".molecule-composition").attr("id"));
$("#modal-marvin").modal();
return false;
}
function apply_molecule_composition_tuning(src){
$(src).find(".molecule-composition")
.keyup(update_molecule_composition_fields)
.prop("required",true)
.change(update_molecule_composition_fields)
.change();
return src;
}
$(document).ready(function(){
apply_molecule_composition_tuning(".formset-item");
var marvinModal = new MarvinModal(
"marviniframe",
'modal-marvin',
'smiles-textarea',
'marvinApplyButton',
function (smilesString){
$("#"+$("#marviniframe").attr("data-smile-target")).val(smilesString).change();
}
);
});
\ No newline at end of file
......@@ -164,15 +164,18 @@ function change_test_selector() {
}
}
function add_form_to_formset(source){
empty_form_as_str = $(source).closest(".formset-encloser").find(".empty_form").prop('innerHTML');
function add_form_to_formset(source, prefix){
let input_total=$(source).closest(".formset-encloser").find("[name='"+prefix+"-TOTAL_FORMS']");
let empty_form_as_str = $(source).closest(".formset-encloser").find(".empty_form").prop('innerHTML');
empty_form_as_str = empty_form_as_str.replace(
/__prefix__/g,
$(source).closest(".formset-encloser").find('.formset-item').length
input_total.val()
);
empty_form=$(empty_form_as_str ).removeClass("empty_form");
empty_form.insertBefore($(source).parent());
empty_form.find("select.test-selector").change(change_test_selector);
input_total.val(parseInt(input_total.val())+1);
return empty_form;
}
function formsetItemDelete(src){
......
{% extends "add.html" %}
{% load i18n %}
{% load customtags %}
{% load sstatic %}
{% block extra_js %}
{{block.super}}
<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>
<script src="/static/js/ippidb-compoundws.js"></script>
<script src="{% sstatic '/js/ippidb-marvinjsmodal.js'%}"></script>
<script src="{% sstatic '/js/compound_form.js'%}"></script>
{% endblock extra_js %}
{% block form %}
<div class="form_div form_div-fluid">
{% if wizard.form.forms is not None %}
{{wizard.form|bootstrap}}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{% include "compound_form_content.html" with form=form%}
......@@ -12,11 +25,16 @@
{% endif %}
<div class="form_div">
<input type="button" id="ck-button-long" class="add-test" value="Add Compound"
onclick="add_form_to_formset(this);"/>
onclick="apply_molecule_composition_tuning(add_form_to_formset(this,'{{wizard.form.prefix}}'));"/>
</div>
</div>
{% endblock %}
{% block emptyform %}
{% include "compound_form_content.html" with form=wizard.form.empty_form %}
{% endblock emptyform%}
\ No newline at end of file
{% endblock emptyform%}
{% block content%}
{{block.super}}
{% include "marvinjs_modal.html" with smiles=similar_to_query fingerprint=similar_to_fingerprint use_fingerprints=False %}
{% endblock content%}
\ No newline at end of file
......@@ -20,7 +20,7 @@ onloadTest("{{complex_type}}", "{{complex_choice}}");
{% endif %}
<div class="form_div">
<input type="button" id="ck-button-long" class="add-test" value="Add Test"
onclick="add_form_to_formset(this);"/>
onclick="add_form_to_formset(this,'{{wizard.form.prefix}}');"/>
</div>
</div>
{% endblock %}
......
......@@ -5,6 +5,7 @@
{% block title %}inhibitors of Protein-Protein Interaction Database{% endblock %}
{% block extra_js %}
{{block.super}}
<script type="text/javascript">
window.onload = function() {
$(".formset-item-delete:checked").each(function(i,o){formsetItemDelete(o);});
......
......@@ -13,26 +13,18 @@
{% endfor %}
{{ form.common_name|bootstrap }}
{{ form.compound_name|bootstrap }}
<div class="type_title">
<h3 class="{%if form.molecule.errors%} is-invalid{%endif%}">Choose a format to import your molecule</h3>
</div>
<div class="input_field form-group">
<div class="compound_mol form-control{%if form.molecule.errors%} is-invalid{%endif%}">
{% for radio in form.molecule %}
<div class="compound_radio compound_radio_{{ radio.choice_label }}">
{{ radio.tag }}
<label for="{{ radio.id_for_label }}">
<span>{{ radio.choice_label }}</span>
</label>
</div>
{% endfor %}
{{ form.is_macrocycle|bootstrap }}
<div class="smiles-inpout-and-edit">
<div>
{{ form.molecule_smiles|bootstrap }}
</div>
<div>
<button class="btn btn-primary" onclick="showModalFromMe(this);return false;">
<i class="fas fa-pencil-alt"></i>
</button>
</div>
{% for e in form.molecule.errors %}
<div class="invalid-feedback">{{e}}</div>
{%endfor%}
</div>
{{ form.molecule_comp|bootstrap }}
{{ form.is_macrocycle|bootstrap }}
{{ form.molecule_iupac|bootstrap }}
</div>
</div>
\ No newline at end of file
import logging
from django import forms
from django import template
from django.forms import BoundField, widgets
from django.utils.safestring import mark_safe
register = template.Library()
......@@ -18,7 +18,7 @@ def url_replace(request, field, value):
@register.filter
def bootstrap(object):
ret = []
if isinstance(object, BoundField):
if isinstance(object, forms.BoundField):
field = object
attrs = field.field.widget.attrs
# get the class specified in the code
......@@ -32,9 +32,9 @@ def bootstrap(object):
# we fetch the "help_text" attribute from wherever we can
help_text = getattr(field, "help_text", None) or attrs.get("help_text", None)
if isinstance(field.field.widget, widgets.CheckboxInput):
if isinstance(field.field.widget, forms.widgets.CheckboxInput):
# if it is a checkbox, we the classes are not the same
wrapping_classes = "form-check"
wrapping_classes = "form-group form-check"
label_classes = "form-check-label"
css_classes.add("form-check-input")
# elif isinstance(field.field.widget, widgets.RadioSelect):
......@@ -67,11 +67,15 @@ def bootstrap(object):
ret.append("""<div class="invalid-feedback">%s</div>""" % e)
ret.append('</div>')
# return it as safe html code
return mark_safe("".join(ret))
elif isinstance(object, forms.BaseFormSet):
formset = object
print(formset.errors)
for error in formset.non_form_errors():
ret.append("""<div class="alert alert-danger">%s</div>""" % error.replace("\n", "<br/>"))
else:
logger.error("Can't bootstrapize object of class %s" % str(type(object)))
return object
ret.append("""<div class="alert alert-danger">Can't bootstrapize object of class %s</div>""" %
str(type(object).__name__))
return mark_safe("".join(ret))
@register.filter('startswith')
......
......@@ -11,12 +11,12 @@ register = template.Library()
@register.simple_tag
def sstatic(path):
def sstatic(path, always_fresh=True):
'''
Returns absolute URL to static file with versioning.
'''
full_path = os.path.join(settings.STATIC_URL, path[1:] if path[0] == '/' else path)
if not settings.DEBUG:
if not settings.DEBUG or not always_fresh:
return full_path
try:
# Get file modification time.
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-01 13:55+0000\n"
"POT-Creation-Date: 2019-02-04 14:25+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -24,10 +24,38 @@ msgstr "Compound Name"
msgid "compound_name_help_text"
msgstr "The name as it can be found in the publication"
msgid "molecule_smiles_label"
msgstr "Molecule composition as SMILES"
msgid "molecule_smiles_help_text"
msgstr "Either provide IUPAC or smiles but not both"
msgid "_molecule_iupac_label"
msgstr "Molecule composition as IUPAC"
msgid "molecule_iupac_help_text"
msgstr "Either provide IUPAC or smiles but not both"
#, python-format
msgid ""
"Compound must have distinct compound_name_label as (explanation). "
"Incriminated value:'%s'"
msgstr ""
"Compound must have distinct 'Compound Name' as it is how they distinguished "
"by it in the publication.\n"
"Incriminated compound name: <b>%s</b>"
#, python-format
msgid "Must match pattern %s for this selected source"
msgstr ""
msgid "is_macrocycle_verbose_name"
msgstr "Contains one or more macrocycles"
# TODO
msgid "is_macrocycle_help_text"
msgstr ""
msgid "Get Infos"
msgstr ""
......
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