Commit d2ec2513 authored by Rachel's avatar Rachel
Browse files

Merge branch 'master' of gitlab.pasteur.fr:ippidb/ippidb-web

parents 3b109654 e231ff32
Pipeline #42105 failed with stages
in 12 minutes and 6 seconds
......@@ -54,7 +54,8 @@ extensions = [
'sphinx.ext.intersphinx',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.graphviz'
'sphinx.ext.graphviz',
'sphinxcontrib.bibtex'
]
# Add any paths that contain templates here, relative to this directory.
......
......@@ -18,6 +18,7 @@ These pages contain the documentation for technical maintenance and development
install
configuration
database
webservices
modules
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -172,20 +172,6 @@ class IdForm(forms.Form):
.first()
)
# def get_or_create(self):
# b = models.Bibliography.objects \
# .filter(id_source=self.cleaned_data["id_source"]).filter(source=self.cleaned_data["source"]) \
# .annotate(used=Count("refcompoundbiblio")) \
# .filter(used=0) \
# .first()
# if b is not None:
# return b, False
# else:
# return models.Bibliography.objects.create(
# source=self.cleaned_data["source"],
# id_source=self.cleaned_data["id_source"],
# ), True
""" Step 2 : BibliographyForm """
......@@ -446,7 +432,6 @@ class ComplexCompositionBaseFormSet(forms.BaseFormSet):
form_kwargs["nb_copies_in_complex"] = nb_copies_in_complex
self.has_partner = has_partner
self.has_bound = has_bound
print("kwargs", form_kwargs)
super().__init__(form_kwargs=form_kwargs, *args, **kwargs)
......@@ -479,7 +464,7 @@ class PpiModelForm(ModelForm):
"data-olsontology": "mondo",
"data-selectpath": "https://www.ebi.ac.uk/ols/",
"olstype": "",
"class": ""
"class": "",
}
),
)
......@@ -489,12 +474,6 @@ class PpiModelForm(ModelForm):
required=False,
)
# other_diseases = forms.CharField(
# label=_("other_diseases_label"),
# help_text=_("other_diseases_help_text"),
# required=False,
# )
class Meta:
model = models.Ppi
fields = (
......@@ -506,7 +485,6 @@ class PpiModelForm(ModelForm):
"pockets_nb", # pockets_nb is Ppi.pockets_nb in the xlsx file of #33
"ols_diseases",
"selected_diseases",
# 'other_diseases',
)
widgets = {
"id": forms.HiddenInput(),
......@@ -533,17 +511,6 @@ class PpiModelForm(ModelForm):
self.fields["symmetry"].initial = symmetry.first()
self.fields["symmetry"].widget = forms.HiddenInput()
pdb_id = initial.get("pdb_id", None)
if pdb_id is not None:
# FIXME move PFAM choice to relevant form ;)
pfams = ws.get_pdb_pfam_mapping(pdb_id) # noqa: F841
# choices = self.fields["family_name"].choices
# for key, values in pfams.items():
# text = "{} ({})".format(values["name"], key)
# choices.append((text, text))
# self.fields["family_name"].choices = choices
# self.fields["family_name"].initial = choices[-1][0]
def full_clean(self):
super().full_clean()
if hasattr(self, "cleaned_data") and "family_name" in self.cleaned_data:
......@@ -571,13 +538,6 @@ class PpiModelForm(ModelForm):
name=name, identifier=identifier
)
self.instance.diseases.add(disease)
# for new_disease in self.cleaned_data['other_diseases'].split(','):
# new_disease = new_disease.strip()
# if len(new_disease) == 0:
# continue
# print(new_disease)
# disease, created = Disease.objects.get_or_create(name=new_disease)
# self.instance.diseases.add(disease)
self.instance.family = models.PpiFamily.objects.get(
id=self.cleaned_data["family"]
)
......@@ -878,6 +838,11 @@ class CompoundActivityResultForm(ModelForm):
"activity": widgets.HiddenInput(),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for f in self.fields.values():
f.widget.attrs.update({"class": "col-3"})
def has_changed(self):
"""
Test if the form has changed, we consider that it has not changed if it is not linked to an actual instance and
......@@ -1049,7 +1014,7 @@ CompoundActivityResultInlineFormset = inlineformset_factory(
class TestActivityDescriptionForm(forms.ModelForm):
test_name = CharFieldDataList(
data_class=models.TestActivityDescription, data_attr="test_name"
data_class=models.TestActivityDescription, data_attr="test_name",
)
cell_line_name = CharFieldDataList(
......@@ -1074,6 +1039,11 @@ class TestActivityDescriptionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["test_type"].widget.attrs["onchange"] = "test_type_changed(this);"
self.fields["test_type"].widget.attrs.update({"class": "col-2"})
self.fields["test_name"].widget.attrs.update({"class": "col-10"})
self.fields["test_modulation_type"].widget.attrs.update({"class": "col-2"})
self.fields["nb_active_compounds"].widget.attrs.update({"class": "col-3"})
self.fields["cell_line_name"].widget.attrs.update({"class": "col-7"})
def has_changed(self):
"""
......@@ -1201,6 +1171,11 @@ class CompoundCytotoxicityResultForm(ModelForm):
fields = ("compound_name", "toxicity")
widgets = {"toxicity": forms.Select(choices=((True, "True"), (False, "False")))}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for f in self.fields.values():
f.widget.attrs.update({"class": "col-3"})
def has_changed(self):
"""
Test if the form has changed, we consider that it has not changed if it is not linked to an actual instance and
......@@ -1315,6 +1290,9 @@ class TestCytotoxDescriptionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["compound_concentration"].required = True
self.fields["cell_line_name"].widget.attrs.update({"class": "col"})
self.fields["compound_concentration"].widget.attrs.update({"class": "col"})
self.fields["test_name"].widget.attrs.update({"class": "col"})
def has_changed(self):
"""
......@@ -1413,6 +1391,13 @@ class CompoundPKResultForm(ModelForm):
)
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for f in self.fields.values():
f.widget.attrs.update({"class": "col-3"})
if isinstance(f, forms.BooleanField) and not isinstance(f.widget, forms.Select):
f.widget.attrs.update({"class": "col-3 mb-4"})
def has_changed(self):
"""
Test if the form has changed, we consider that it has not changed if it is not linked to an actual instance and
......@@ -1524,6 +1509,8 @@ class TestPKDescriptionForm(forms.ModelForm):
super().__init__(*args, **kwargs)
for f in ["administration_mode", "dose"]:
self.fields[f].required = True
for f in self.fields.values():
f.widget.attrs.update({"class": "col-3"})
def has_changed(self):
"""
......
......@@ -3,13 +3,13 @@
from django.db import migrations, models
import django.db.models.deletion
from ippidb.models import Compound, DrugBankCompound
def run_generate_drugbank_inchikey(apps, schema_editor):
DrugBankCompound = apps.get_model("ippidb", "DrugBankCompound")
for drugbank_compound in DrugBankCompound.objects.all():
drugbank_compound.save(autofill=True)
def set_compound_drugbank_links(apps, schema_editor):
Compound = apps.get_model("ippidb", "Compound")
for compound in Compound.objects.all():
compound.set_drugbank_link()
compound.save()
......
# Generated by Django 2.2.1 on 2020-09-14 18:02
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('ippidb', '0064_proteindomaincomplex_polymorphic_ctype'),
]
operations = [
migrations.AddField(
model_name='compound',
name='replaced_with',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='ippidb.Compound', verbose_name='Replacement ID'),
),
]
......@@ -8,6 +8,7 @@ import operator
import re
import sys
from boltons.iterutils import flatten, unique_iter
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
......@@ -570,11 +571,13 @@ class CompoundsManager(models.Manager):
qs = self.get_queryset()
if current_user.is_anonymous:
qs = qs.exclude(compoundaction__ppi__contribution__validated=False)
qs = qs.filter(replaced_with__isnull=True)
elif not current_user.is_superuser:
qs = qs.exclude(
Q(compoundaction__ppi__contribution__validated=False),
~Q(compoundaction__ppi__contribution__contributor=current_user),
)
qs = qs.filter(replaced_with__isnull=True)
return qs
def validated(self):
......@@ -865,6 +868,14 @@ class Compound(AutoFillableModel):
verbose_name="Number of tests available", null=True, blank=True
)
replaced_with = models.ForeignKey(
"self",
verbose_name="Replacement ID",
null=True,
blank=True,
on_delete=models.PROTECT,
)
class Meta:
ordering = ["id"]
indexes = [
......@@ -975,6 +986,20 @@ class Compound(AutoFillableModel):
.count()
)
@property
def pk_tests_count(self):
"""
Return the number of associated pharmacokinetic tests
"""
return self.compoundpkresult_set.all().count()
@property
def cytotoxicity_tests_count(self):
"""
Return the number of associated cytotoxicity tests
"""
return self.compoundcytotoxicityresult_set.all().count()
@property
def sorted_similar_drugbank_compounds(self):
"""
......@@ -1072,6 +1097,59 @@ class Compound(AutoFillableModel):
"""
return reverse("compound_card", kwargs={"pk": self.pk})
@property
def pubchem_url(self):
"""
Get absolute URL to the corresponding PubChem entry
"""
return (
f"https://pubchem.ncbi.nlm.nih.gov/compound/{ self.pubchem_id }"
if self.pubchem_id
else None
)
@property
def chembl_url(self):
"""
Get absolute URL to the corresponding Chembl entry
"""
return (
f"https://www.ebi.ac.uk/chembldb/compound/inspect/{ self.chembl_id }"
if self.chembl_id
else None
)
@property
def chemspider_url(self):
"""
Get absolute URL to the corresponding ChemSpider entry
"""
return (
f"http://www.chemspider.com/Chemical-Structure.{ self.chemspider_id}.html"
if self.chemspider_id
else None
)
@property
def pdbligand_url(self):
"""
Get absolute URL to the corresponding PDB ligand entry
"""
return (
f"https://www.rcsb.org/ligand/{ self.ligand_id}" if self.ligand_id else None
)
@property
def drugbank_url(self):
"""
Get absolute URL to the corresponding Drugbank entry
"""
return (
f"https://www.drugbank.ca/drugs/{ self.drugbank_id}"
if self.drugbank_id
else None
)
def clean(self):
"""
Perform additional checks:
......@@ -1097,7 +1175,9 @@ class Compound(AutoFillableModel):
Replace the references to a given compound in the data with
references to another new compound. used to deal with
duplicates in the database
also delete this object
"""
self.replaced_with = replacing_compound
for ref in RefCompoundBiblio.objects.filter(compound=self):
ref.compound = replacing_compound
ref.save()
......@@ -1118,6 +1198,60 @@ class Compound(AutoFillableModel):
CompoundTanimoto.objects.filter(compound=self).delete()
DrugbankCompoundTanimoto.objects.filter(compound=self).delete()
def get_target_activities_table(self):
"""
Return test activity result data as a list of
items containing for each PPI family the compound
was tested against, the best activity, the linked diseases
and the modulation types
"""
ppi_families = [
item["test_activity_description__ppi__family"]
for item in self.compoundactivityresult_set.filter(
~Q(activity_type="KdRat")
)
.order_by()
.values("test_activity_description__ppi__family")
.distinct()
]
results = []
for ppi_family in ppi_families:
family_compound_activity_results = self.compoundactivityresult_set.filter(
~Q(activity_type="KdRat")
).filter(test_activity_description__ppi__family__id=ppi_family)
best_activity = (
family_compound_activity_results.order_by("-activity")
.values("activity")
.first()["activity"]
)
diseases = list(
unique_iter(
flatten(
[
list(fcar.test_activity_description.ppi.diseases.all())
for fcar in family_compound_activity_results
]
)
)
)
modulation_types = list(
set(
[
fcar.get_modulation_type_display()
for fcar in family_compound_activity_results
]
)
)
results.append(
{
"family": PpiFamily.objects.get(id=ppi_family),
"best_activity": best_activity,
"diseases": diseases,
"modulation_types": modulation_types,
}
)
return results
class CompoundTanimoto(models.Model):
canonical_smiles = models.TextField("Canonical Smile")
......@@ -1495,21 +1629,14 @@ class ContributionManager(models.Manager):
if current_user.is_anonymous:
qs = qs.exclude(validated=False)
elif not current_user.is_superuser:
qs = qs.exclude(
Q(validated=False),
~Q(contributor=current_user),
)
qs = qs.exclude(Q(validated=False), ~Q(contributor=current_user),)
return qs
def validated(self):
"""
Get validated contributions
"""
return (
super()
.get_queryset()
.exclude(validated=False)
)
return super().get_queryset().exclude(validated=False)
class Contribution(models.Model):
......@@ -1551,11 +1678,6 @@ def update_compound_cached_properties(compounds_queryset=None):
)
c.save()
compounds_queryset.update(
pubs=Subquery(
compounds_queryset.filter(id=OuterRef("id"))
.annotate(_pubs=Count("refcompoundbiblio", distinct=True))
.values("_pubs")[:1]
),
best_activity=Subquery(
compounds_queryset.filter(id=OuterRef("id"))
.annotate(
......@@ -1566,23 +1688,23 @@ def update_compound_cached_properties(compounds_queryset=None):
)
.values("_best_activity")[:1]
),
)
compounds_queryset.update(
pubs=Subquery(
compounds_queryset.filter(id=OuterRef("id"))
.annotate(_pubs=Count("refcompoundbiblio", distinct=True))
.values("_pubs")[:1]
),
le=Subquery(
compounds_queryset.filter(id=OuterRef("id"))
.annotate(
_le=Cast(
1.37 * Max("compoundactivityresult__activity") / F("nb_atom_non_h"),
FloatField(),
)
_le=Cast(1.37 * F("best_activity") / F("nb_atom_non_h"), FloatField(),)
)
.values("_le")[:1]
),
lle=Subquery(
compounds_queryset.filter(id=OuterRef("id"))
.annotate(
_lle=Cast(
Max("compoundactivityresult__activity") - F("a_log_p"), FloatField()
)
)
.annotate(_lle=Cast(F("best_activity") - F("a_log_p"), FloatField()))
.values("_lle")[:1]
),
lipinsky_mw=Subquery(
......@@ -1814,11 +1936,9 @@ def update_compound_cached_properties(compounds_queryset=None):
)
.values("_bindtest_av")[:1]
),
pktest_av=Exists(
CompoundPKResult.objects.filter(id=OuterRef("id"))
),
pktest_av=Exists(CompoundPKResult.objects.filter(compound__id=OuterRef("id"))),
cytoxtest_av=Exists(
CompoundCytotoxicityResult.objects.filter(id=OuterRef("id"))
CompoundCytotoxicityResult.objects.filter(compound__id=OuterRef("id"))
),
insilico_av=Subquery(
compounds_queryset.filter(id=OuterRef("id"))
......
......@@ -225,7 +225,7 @@ Description: IPPI-DB Theme
.input_field {
width: 100%;
margin-right: 2px;
margin-right: 0px;
font-family: "BrandonGrotesqueReg";
text-align: left;
margin-top: 0px;
......@@ -268,6 +268,10 @@ Description: IPPI-DB Theme
padding-bottom: 1%;
}
.form-group.form-check{
padding-left: 2.25rem;
}
.form-group.form-check, .compound .input_field input {
text-align: left;
}
......@@ -356,6 +360,9 @@ Description: IPPI-DB Theme
.formset-item .formset-item-delete-host {
font-size: initial;
text-align: right;
position: absolute;
right: 3em;
margin-top: -4em;
}
.formset-item-delete-host input {
......@@ -1352,6 +1359,7 @@ Description: IPPI-DB Theme
.test_title {
text-align: left;
margin-top: 15px;
margin-bottom: 1.5em;
}
.tests {
......
function test_type_changed(source){
var cell_line_input = document.getElementById(source.id.replace("test_type","cell_line_name")).parentNode;
if (source.value=="CELL"){
cell_line_input.style.display = '';
cell_line_input.style.visibility = '';
}else{
cell_line_input.style.display = 'none';
cell_line_input.style.visibility = 'hidden';
}
}
\ No newline at end of file
......@@ -23,7 +23,11 @@ function add_nested_form_to_nested_formset(source, prefix, parent_prefix){
.find(".formset-nested-item")[0]
.outerHTML;
current_item_name = $(source).closest(".formset-item").children("input")[0].name;
if($(source).closest(".formset-item").children("input").length>0){
current_item_name = $(source).closest(".formset-item").children("input")[0].name;
}else{
current_item_name = $(source).closest(".formset-item").children("div").children("input")[0].name;
}
current_item_name = current_item_name.substring(parent_prefix.length+1,current_item_name.indexOf('-',parent_prefix.length+1));
empty_form_as_str = empty_form_as_str.replace(
new RegExp(parent_prefix+"-__prefix__","g"),
......
......@@ -625,7 +625,7 @@ def launch_validate_contributions(contribution_ids: List[int]):
run_update_compound_cached_properties.s(),
run_compute_drugbank_similarity.s(),
run_validate.s(),
)
),
)
contribution_jobs.append(compounds_properties_computation_group)
# compounds_properties_computation_group.delay()
......
......@@ -6,7 +6,8 @@
<script src="/static/js/ippidb-charts.js" type="text/javascript"></script>
{% endblock %}
{% block title %}compound {{ compound.id }} detail{% if compound.common_name %} ({{compound.common_name}}){% endif %}{% endblock %}
{% block title %}compound {{ compound.id }} detail{% if compound.common_name %}
({{compound.common_name}}){% endif %}{% endblock %}
{% block content %}
<div id="mainnav">
......@@ -36,7 +37,9 @@
{% include "compound_smiles_draw.html" with id=compound.id smile=compound.canonical_smile width=250 height=250%}
<div style="display: flex;
align-items: center;">
{% if compound.is_validated is False %}
{% if compound.replaced_with is not None %}
<h1 class="page-title-warning" style="margin: 0;">Compound {{ compound.id }} - This compound is replaced with <a href="/compounds/{{ compound.replaced_with.id }}">compound {{ compound.replaced_with.id }}</a> </h1>
{% elif compound.is_validated is False %}
<h1 class="page-title-warning" style="margin: 0;">Compound {{ compound.id }} - This compound has not been
validated by a curator yet, data might be incomplete or
inaccurate</h1>
......@@ -52,7 +55,7 @@
<div class="row" style="display: inline-block;height:auto; margin-right: 15px; margin-left: 15px;">
<div class="tab-content" id="tabContent">
<div class="tab-pane fade show active" id="compound" role="tabpanel" aria-labelledby="compound-tab">
<div class="card">
<div class="card inner-wrap">
<div class="card-body">
{% include "compound_l_item.html" with compound=compound show_detail=True %}
</div>
......@@ -61,7 +64,8 @@
<div class="tab-pane fade" id="physicochemistry" role="tabpanel" aria-labelledby="physicochemistry-tab">
<div class="row d-flex justify-content-center">
<div class="col-sm-12 col-md-9" style="margin: 10px;">
<h5 class="card_title">Physicochemical filters <span class="info_point" onclick="showLegend('show_legend_physico')"><i class="fas fa-info-circle"></i></span>
<h5 class="card_title">Physicochemical filters <span class="info_point"
onclick="showLegend('show_legend_physico')"><i class="fas fa-info-circle"></i></span>
</h5>
<div id="show_legend_physico" style="display: none; padding:8px;">
<div class="row d-flex justify-content-center" style=" box-shadow: 0 3px 11px 0 rgba(0, 0, 0, 0.2);
......@@ -212,7 +216,7 @@
<td class="text-center {% status_class compound.veber_tpsa %}" title="TPSA <= 140">
</td>
<td class="text-center {% status_class compound.pfizer_tpsa %}" title="TPSA >= 75">
</tr>
</tr>
<tr>
<th scope="row">RB</th>
<td>{{ compound.nb_rotatable_bonds | default_if_none:"not available"}}</td>
......@@ -224,94 +228,96 @@
</tbody>
</table>