Basic System (#31)

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#31
This commit is contained in:
2025-07-23 06:47:07 +12:00
parent 49e02ed97d
commit df896878f1
75 changed files with 3821 additions and 1429 deletions

View File

@ -15,6 +15,7 @@ from pathlib import Path
from dotenv import load_dotenv
from envipy_plugins import Classifier, Property, Descriptor
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -256,14 +257,29 @@ CELERY_RESULT_BACKEND = 'redis://localhost:6379/1'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
DEFAULT_MODELS_PARAMS = {
'base_clf': RandomForestClassifier(n_estimators=100,
DEFAULT_RF_MODEL_PARAMS = {
'base_clf': RandomForestClassifier(
n_estimators=100,
max_features='log2',
random_state=42,
criterion='entropy',
ccp_alpha=0.0,
max_depth=3,
min_samples_leaf=1),
min_samples_leaf=1
),
'num_chains': 10,
}
DEFAULT_DT_MODEL_PARAMS = {
'base_clf': DecisionTreeClassifier(
criterion='entropy',
max_depth=3,
min_samples_split=5,
min_samples_leaf=5,
max_features='sqrt',
class_weight='balanced',
random_state=42
),
'num_chains': 10,
}

View File

@ -7,13 +7,21 @@ from django.db import transaction
from django.conf import settings as s
from epdb.models import User, Package, UserPackagePermission, GroupPackagePermission, Permission, Group, Setting, \
EPModel, UserSettingPermission, Rule, Pathway, Node, Edge
EPModel, UserSettingPermission, Rule, Pathway, Node, Edge, Compound, Reaction, CompoundStructure
from utilities.chem import FormatConverter
logger = logging.getLogger(__name__)
class UserManager(object):
user_pattern = re.compile(r".*/user/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")
@staticmethod
def create_user(username, email, password, *args, **kwargs):
def is_user_url(url: str):
return bool(re.findall(UserManager.user_pattern, url))
@staticmethod
def create_user(username, email, password, set_setting=True, add_to_group=True, *args, **kwargs):
# avoid circular import :S
from .tasks import send_registration_mail
@ -34,6 +42,17 @@ class UserManager(object):
# send email for verification
send_registration_mail.delay(u.pk)
if set_setting:
u.default_setting = Setting.objects.get(global_default=True)
u.save()
if add_to_group:
g = Group.objects.get(public=True, name='enviPath Users')
g.user_member.add(u)
g.save()
u.default_group = g
u.save()
return u
@staticmethod
@ -54,7 +73,16 @@ class UserManager(object):
uuid = user_url.strip().split('/')[-1]
return get_user_model().objects.get(uuid=uuid)
@staticmethod
def writable(current_user, user):
return (current_user == user) or user.is_superuser
class GroupManager(object):
group_pattern = re.compile(r".*/group/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")
@staticmethod
def is_group_url(url: str):
return bool(re.findall(GroupManager.group_pattern, url))
@staticmethod
def create_group(current_user, name, description):
@ -110,9 +138,17 @@ class GroupManager(object):
group.save()
@staticmethod
def writable(user, group):
return (user == group.owner) or user.is_superuser
class PackageManager(object):
package_pattern = re.compile(r".*/package/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
package_pattern = re.compile(r".*/package/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")
@staticmethod
def is_package_url(url: str):
return bool(re.findall(PackageManager.package_pattern, url))
@staticmethod
def get_reviewed_packages():
@ -120,7 +156,6 @@ class PackageManager(object):
@staticmethod
def readable(user, package):
# TODO Owner!
if UserPackagePermission.objects.filter(package=package, user=user).exists() or \
GroupPackagePermission.objects.filter(package=package, group__in=GroupManager.get_groups(user)) or \
package.reviewed is True or \
@ -131,14 +166,21 @@ class PackageManager(object):
@staticmethod
def writable(user, package):
# TODO Owner!
if UserPackagePermission.objects.filter(package=package, user=user, permission=Permission.WRITE).exists() or \
GroupPackagePermission.objects.filter(package=package, group__in=GroupManager.get_groups(user),
permission=Permission.WRITE) or \
if UserPackagePermission.objects.filter(package=package, user=user, permission=Permission.WRITE[0]).exists() or \
GroupPackagePermission.objects.filter(package=package, group__in=GroupManager.get_groups(user), permission=Permission.WRITE[0]).exists() or \
UserPackagePermission.objects.filter(package=package, user=user, permission=Permission.ALL[0]).exists() or \
user.is_superuser:
return True
return False
@staticmethod
def get_package_lp(package_url):
match = re.findall(PackageManager.package_pattern, package_url)
if match:
package_id = match[0].split('/')[-1]
return Package.objects.get(uuid=package_id)
return None
@staticmethod
def get_package_by_url(user, package_url):
match = re.findall(PackageManager.package_pattern, package_url)
@ -229,8 +271,12 @@ class PackageManager(object):
@staticmethod
@transaction.atomic
def update_permissions(caller: User, package: Package, grantee: Union[User, Group], new_perm: Optional[str]):
if not PackageManager.writable(caller, package):
raise ValueError(f"User {caller} is not allowed to modify permissions on {package}")
caller_perm = None
if not caller.is_superuser:
caller_perm = UserPackagePermission.objects.get(user=caller, package=package).permission
if caller_perm != Permission.ALL[0] and not caller.is_superuser:
raise ValueError(f"Only owner are allowed to modify permissions")
data = {
'package': package,
@ -629,8 +675,6 @@ class PackageManager(object):
return pack
class SettingManager(object):
setting_pattern = re.compile(r".*/setting/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
@ -648,7 +692,8 @@ class SettingManager(object):
def get_setting_by_id(user, setting_id):
s = Setting.objects.get(uuid=setting_id)
if s.global_default or s.public or s.owner == user or user.is_superuser:
if s.global_default or s.public or user.is_superuser or \
UserSettingPermission.objects.filter(user=user, setting=s).exists():
return s
raise ValueError(
@ -697,6 +742,116 @@ class SettingManager(object):
def set_default_setting(user: User, setting: Setting):
pass
class SearchManager(object):
@staticmethod
def search(packages: Union[Package, List[Package]], searchterm: str, mode: str):
match mode:
case 'text':
return SearchManager._search_text(packages, searchterm)
case 'default':
return SearchManager._search_default_smiles(packages, searchterm)
case 'exact':
return SearchManager._search_exact_smiles(packages, searchterm)
case 'canonical':
return SearchManager._search_canonical_smiles(packages, searchterm)
case 'inchikey':
return SearchManager._search_inchikey(packages, searchterm)
case _:
raise ValueError(f"Unknown search mode {mode}!")
@staticmethod
def _search_inchikey(packages: Union[Package, List[Package]], searchterm: str):
from django.db.models import Q
search_cond = Q(inchikey=searchterm)
compound_qs = Compound.objects.filter(Q(package__in=packages) & Q(compoundstructure__inchikey=searchterm)).distinct()
compound_structure_qs = CompoundStructure.objects.filter(Q(compound__package__in=packages) & search_cond)
reactions_qs = Reaction.objects.filter(Q(package__in=packages) & (Q(educts__inchikey=searchterm) | Q(products__inchikey=searchterm))).distinct()
pathway_qs = Pathway.objects.filter(Q(package__in=packages) & (Q(edge__edge_label__educts__inchikey=searchterm) | Q(edge__edge_label__products__inchikey=searchterm))).distinct()
return {
'Compounds': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_qs],
'Compound Structures': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_structure_qs],
'Reactions': [{'name': r.name, 'description': r.description, 'url': r.url} for r in reactions_qs],
'Pathways': [{'name': p.name, 'description': p.description, 'url': p.url} for p in pathway_qs],
}
@staticmethod
def _search_exact_smiles(packages: Union[Package, List[Package]], searchterm: str):
from django.db.models import Q
search_cond = Q(smiles=searchterm)
compound_qs = Compound.objects.filter(Q(package__in=packages) & Q(compoundstructure__smiles=searchterm)).distinct()
compound_structure_qs = CompoundStructure.objects.filter(Q(compound__package__in=packages) & search_cond)
reactions_qs = Reaction.objects.filter(Q(package__in=packages) & (Q(educts__smiles=searchterm) | Q(products__smiles=searchterm))).distinct()
pathway_qs = Pathway.objects.filter(Q(package__in=packages) & (Q(edge__edge_label__educts__smiles=searchterm) | Q(edge__edge_label__products__smiles=searchterm))).distinct()
return {
'Compounds': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_qs],
'Compound Structures': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_structure_qs],
'Reactions': [{'name': r.name, 'description': r.description, 'url': r.url} for r in reactions_qs],
'Pathways': [{'name': p.name, 'description': p.description, 'url': p.url} for p in pathway_qs],
}
@staticmethod
def _search_default_smiles(packages: Union[Package, List[Package]], searchterm: str):
from django.db.models import Q
inchi_front = FormatConverter.InChIKey(searchterm)[:14]
search_cond = Q(inchikey__startswith=inchi_front)
compound_qs = Compound.objects.filter(Q(package__in=packages) & Q(compoundstructure__inchikey__startswith=inchi_front)).distinct()
compound_structure_qs = CompoundStructure.objects.filter(Q(compound__package__in=packages) & search_cond)
reactions_qs = Reaction.objects.filter(Q(package__in=packages) & (Q(educts__inchikey__startswith=inchi_front) | Q(products__inchikey__startswith=inchi_front))).distinct()
pathway_qs = Pathway.objects.filter(Q(package__in=packages) & (Q(edge__edge_label__educts__inchikey__startswith=inchi_front) | Q(edge__edge_label__products__inchikey__startswith=inchi_front))).distinct()
return {
'Compounds': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_qs],
'Compound Structures': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_structure_qs],
'Reactions': [{'name': r.name, 'description': r.description, 'url': r.url} for r in reactions_qs],
'Pathways': [{'name': p.name, 'description': p.description, 'url': p.url} for p in pathway_qs],
}
@staticmethod
def _search_canonical_smiles(packages: Union[Package, List[Package]], searchterm: str):
from django.db.models import Q
search_cond = Q(canonical_smiles=searchterm)
compound_qs = Compound.objects.filter(Q(package__in=packages) & Q(compoundstructure__canonical_smiles=searchterm)).distinct()
compound_structure_qs = CompoundStructure.objects.filter(Q(compound__package__in=packages) & search_cond)
reactions_qs = Reaction.objects.filter(Q(package__in=packages) & (Q(educts__canonical_smiles=searchterm) | Q(products__canonical_smiles=searchterm))).distinct()
pathway_qs = Pathway.objects.filter(Q(package__in=packages) & (Q(edge__edge_label__educts__canonical_smiles=searchterm) | Q(edge__edge_label__products__canonical_smiles=searchterm))).distinct()
return {
'Compounds': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_qs],
'Compound Structures': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_structure_qs],
'Reactions': [{'name': r.name, 'description': r.description, 'url': r.url} for r in reactions_qs],
'Pathways': [{'name': p.name, 'description': p.description, 'url': p.url} for p in pathway_qs],
}
@staticmethod
def _search_text(packages: Union[Package, List[Package]], searchterm: str):
from django.db.models import Q
search_cond = (Q(name__icontains=searchterm) | Q(description__icontains=searchterm))
cond = Q(package__in=packages) & search_cond
compound_qs = Compound.objects.filter(cond)
compound_structure_qs = CompoundStructure.objects.filter(Q(compound__package__in=packages) & search_cond)
rule_qs = Rule.objects.filter(cond)
reactions_qs = Reaction.objects.filter(cond)
pathway_qs = Pathway.objects.filter(cond)
res = {
'Compounds': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_qs],
'Compound Structures': [{'name': c.name, 'description': c.description, 'url': c.url} for c in compound_structure_qs],
'Rules': [{'name': r.name, 'description': r.description, 'url': r.url} for r in rule_qs],
'Reactions': [{'name': r.name, 'description': r.description, 'url': r.url} for r in reactions_qs],
'Pathways': [{'name': p.name, 'description': p.description, 'url': p.url} for p in pathway_qs],
}
return res
class SNode(object):
@ -719,7 +874,7 @@ class SNode(object):
class SEdge(object):
def __init__(self, educts: Union[SNode, List[SNode]], products: Union[SNode | List[SNode]],
rule: Optional['Rule'] = None):
rule: Optional['Rule'] = None, probability: Optional[float] = None):
if not isinstance(educts, list):
educts = [educts]
@ -727,6 +882,7 @@ class SEdge(object):
self.educts = educts
self.products = products
self.rule = rule
self.probability = probability
def __hash__(self):
full_hash = 0
@ -799,11 +955,45 @@ class SPathway(object):
elif isinstance(n, SNode):
self.root_nodes.append(n)
self.queue = list()
self.smiles_to_node: Dict[str, SNode] = dict(**{n.smiles: n for n in self.root_nodes})
self.edges: Set['SEdge'] = set()
self.done = False
@staticmethod
def from_pathway(pw: 'Pathway', persist: bool = True):
""" Initializes a SPathway with a state given by a Pathway """
spw = SPathway(root_nodes=pw.root_nodes, persist=pw if persist else None, prediction_setting=pw.setting)
# root_nodes are already added in __init__, add remaining nodes
for n in pw.nodes:
snode = SNode(n.default_node_label.smiles, n.depth)
if snode.smiles not in spw.smiles_to_node:
spw.smiles_to_node[snode.smiles] = snode
spw.snode_persist_lookup[snode] = n
for e in pw.edges:
sub = []
prod = []
for n in e.start_nodes.all():
sub.append(spw.smiles_to_node[n.default_node_label.smiles])
for n in e.end_nodes.all():
prod.append(spw.smiles_to_node[n.default_node_label.smiles])
rule = None
if e.edge_label.rules.all():
rule = e.edge_label.rules.all().first()
prob = None
if e.kv.get('probability'):
prob = float(e.kv['probability'])
sedge = SEdge(sub, prod, rule=rule, probability=prob)
spw.edges.add(sedge)
spw.sedge_persist_lookup[sedge] = e
return spw
def num_nodes(self):
return len(self.smiles_to_node.keys())
@ -830,8 +1020,18 @@ class SPathway(object):
return sorted(res, key=lambda x: hash(x))
def predict_step(self, from_depth: int = 0):
def predict_step(self, from_depth: int = None, from_node: 'Node' = None):
substrates: List[SNode] = []
if from_depth is not None:
substrates = self._get_nodes_for_depth(from_depth)
elif from_node is not None:
for k,v in self.snode_persist_lookup.items():
if from_node == v:
substrates = [k]
break
else:
raise ValueError("Neither from_depth nor from_node_url specified")
new_tp = False
if substrates:
@ -849,13 +1049,19 @@ class SPathway(object):
node = self.smiles_to_node[c]
cand_nodes.append(node)
edge = SEdge(sub, cand_nodes, cand_set.rule)
edge = SEdge(sub, cand_nodes, rule=cand_set.rule, probability=cand_set.probability)
self.edges.add(edge)
else:
# In case no substrates are found, we're done.
# For "predict from node" we're always done
if len(substrates) == 0 or from_node is not None:
self.done = True
# Check if we need to write back data to database
if new_tp and self.persist:
self._sync_to_pathway()
# call save to update internal modified field
self.persist.save()
def _sync_to_pathway(self):
logger.info("Updating Pathway with SPathway")
@ -876,6 +1082,11 @@ class SPathway(object):
product_nodes.append(self.snode_persist_lookup[snode])
e = Edge.create(self.persist, educt_nodes, product_nodes, sedge.rule)
if sedge.probability:
e.kv.update({'probability': sedge.probability})
e.save()
self.sedge_persist_lookup[sedge] = e
logger.info("Update done!")

View File

@ -13,12 +13,14 @@ class Command(BaseCommand):
def create_users(self):
if not User.objects.filter(email='anon@lorsba.ch').exists():
anon = UserManager.create_user("anonymous", "anon@lorsba.ch", "SuperSafe", is_active=True)
anon = UserManager.create_user("anonymous", "anon@lorsba.ch", "SuperSafe", is_active=True,
add_to_group=False, set_setting=False)
else:
anon = User.objects.get(email='anon@lorsba.ch')
if not User.objects.filter(email='admin@lorsba.ch').exists():
admin = UserManager.create_user("admin", "admin@lorsba.ch", "SuperSafe", is_active=True)
admin = UserManager.create_user("admin", "admin@lorsba.ch", "SuperSafe", is_active=True, add_to_group=False,
set_setting=False)
admin.is_staff = True
admin.is_superuser = True
admin.save()
@ -26,6 +28,9 @@ class Command(BaseCommand):
admin = User.objects.get(email='admin@lorsba.ch')
g = GroupManager.create_group(admin, 'enviPath Users', 'All enviPath Users')
g.public = True
g.save()
g.user_member.add(anon)
g.save()
@ -36,7 +41,8 @@ class Command(BaseCommand):
admin.save()
if not User.objects.filter(email='jebus@lorsba.ch').exists():
jebus = UserManager.create_user("jebus", "jebus@lorsba.ch", "SuperSafe", is_active=True)
jebus = UserManager.create_user("jebus", "jebus@lorsba.ch", "SuperSafe", is_active=True, add_to_group=False,
set_setting=False)
jebus.is_staff = True
jebus.is_superuser = True
jebus.save()
@ -94,6 +100,9 @@ class Command(BaseCommand):
setting.make_global_default()
for u in [anon, jebus]:
u.default_setting = setting
u.save()
usp = UserSettingPermission()
usp.user = u
usp.setting = setting
@ -119,7 +128,7 @@ class Command(BaseCommand):
X, y = ml_model.build_dataset()
ml_model.build_model(X, y)
ml_model.evaluate_model()
# ml_model.evaluate_model()
# If available create EnviFormerModel
if s.ENVIFORMER_PRESENT:

View File

@ -14,7 +14,7 @@ from django.contrib.auth.hashers import make_password, check_password
from django.contrib.auth.models import AbstractUser
from django.contrib.postgres.fields import ArrayField
from django.db import models, transaction
from django.db.models import JSONField
from django.db.models import JSONField, Count, Q
from django.utils import timezone
from django.utils.functional import cached_property
from model_utils.models import TimeStampedModel
@ -43,8 +43,6 @@ class User(AbstractUser):
on_delete=models.SET_NULL, related_name='default_group')
default_setting = models.ForeignKey('epdb.Setting', on_delete=models.SET_NULL,
verbose_name='The users default settings', null=True, blank=False)
# TODO remove
groups = models.ManyToManyField("Group", verbose_name='groups')
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ['username']
@ -87,6 +85,7 @@ class Group(TimeStampedModel):
uuid = models.UUIDField(null=False, blank=False, verbose_name='UUID of this object', unique=True, default=uuid4)
name = models.TextField(blank=False, null=False, verbose_name='Group name')
owner = models.ForeignKey("User", verbose_name='Group Owner', on_delete=models.CASCADE)
public = models.BooleanField(verbose_name='Public Group', default=False)
description = models.TextField(blank=False, null=False, verbose_name='Descriptions', default='no description')
user_member = models.ManyToManyField("User", verbose_name='User members', related_name='users_in_group')
group_member = models.ManyToManyField("Group", verbose_name='Group member', related_name='groups_in_group')
@ -314,7 +313,7 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin):
@transaction.atomic
def create(package: Package, smiles: str, name: str = None, description: str = None, *args, **kwargs) -> 'Compound':
if smiles is None or smiles == '':
if smiles is None or smiles.strip() == '':
raise ValueError('SMILES is required')
smiles = smiles.strip()
@ -338,12 +337,14 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin):
c = Compound()
c.package = package
# For name and description we have defaults so only set them if they carry a real value
if name is not None and name != '':
if name is None or name.strip() == '':
name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
c.name = name
if description is not None and description != '':
c.description = description
# We have a default here only set the value if it carries some payload
if description is not None and description.strip() != '':
c.description = description.strip()
c.save()
@ -403,25 +404,27 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin):
class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin):
compound = models.ForeignKey('epdb.Compound', on_delete=models.CASCADE, db_index=True)
smiles = models.TextField(blank=False, null=False, verbose_name='SMILES')
canonical_smiles = models.TextField(blank=False, null=False, verbose_name='Canonical SMILES')
inchikey = models.TextField(max_length=27, blank=False, null=False, verbose_name="InChIKey")
normalized_structure = models.BooleanField(null=False, blank=False, default=False)
def save(self, *args, **kwargs):
# Compute these fields only on initial save call
if self.pk is None:
try:
# Generate canonical SMILES
self.canonical_smiles = FormatConverter.canonicalize(self.smiles)
# Generate InChIKey
self.inchikey = FormatConverter.InChIKey(self.smiles)
except Exception as e:
logger.error(f"Could compute canonical SMILES/InChIKey from {self.smiles}, error: {e}")
super().save(*args, **kwargs)
@property
def url(self):
return '{}/structure/{}'.format(self.compound.url, self.uuid)
# @property
# def related_pathways(self):
# pathways = Node.objects.filter(node_labels__in=[self]).values_list('pathway', flat=True)
# return Pathway.objects.filter(package=self.compound.package, id__in=set(pathways)).order_by('name')
# @property
# def related_reactions(self):
# return (
# Reaction.objects.filter(package=self.compound.package, educts__in=[self])
# |
# Reaction.objects.filter(package=self.compound.package, products__in=[self])
# ).order_by('name')
@staticmethod
@transaction.atomic
def create(compound: Compound, smiles: str, name: str = None, description: str = None, *args, **kwargs):
@ -448,19 +451,9 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin):
return cs
# TODO add find method
@property
def InChIKey(self):
return FormatConverter.InChIKey(self.smiles)
@property
def canonical_smiles(self):
return FormatConverter.canonicalize(self.smiles)
@property
def as_svg(self):
return IndigoUtils.mol_to_svg(self.smiles)
def as_svg(self, width: int = 800, height: int = 400):
return IndigoUtils.mol_to_svg(self.smiles, width=width, height=height)
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
@ -492,19 +485,9 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
@staticmethod
@transaction.atomic
def create(package: Package, rule_type: str, name: str = None, description: str = None, *args, **kwargs):
r = Rule.cls_for_type(rule_type)()
r.package = package
r.name = name
r.description = description
# As we are setting params this way the "k" has to match the property name
for k, v in kwargs.items():
setattr(r, k, v)
r.save()
return r
def create(rule_type: str, *args, **kwargs):
cls = Rule.cls_for_type(rule_type)
return cls.create(*args, **kwargs)
#
# @property
@ -533,6 +516,54 @@ class SimpleAmbitRule(SimpleRule):
reactant_filter_smarts = models.TextField(null=True, verbose_name='Reactant Filter SMARTS')
product_filter_smarts = models.TextField(null=True, verbose_name='Product Filter SMARTS')
@staticmethod
@transaction.atomic
def create(package: Package, name: str = None, description: str = None, smirks: str = None,
reactant_filter_smarts: str = None, product_filter_smarts: str = None):
if smirks is None or smirks.strip() == '':
raise ValueError('SMIRKS is required!')
smirks = smirks.strip()
if not FormatConverter.is_valid_smirks(smirks):
raise ValueError(f'SMIRKS "{smirks}" is invalid!')
query = SimpleAmbitRule.objects.filter(package=package, smirks=smirks)
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != '':
query = query.filter(reactant_filter_smarts=reactant_filter_smarts)
if product_filter_smarts is not None and product_filter_smarts.strip() != '':
query = query.filter(product_filter_smarts=product_filter_smarts)
if query.exists():
if query.count() > 1:
logger.error(f'More than one rule matched this one! {query}')
return query.first()
r = SimpleAmbitRule()
r.package = package
if name is None or name.strip() == '':
name = f'Rule {Rule.objects.filter(package=package).count() + 1}'
r.name = name
if description is not None and description.strip() != '':
r.description = description
r.smirks = smirks
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != '':
r.reactant_filter_smarts = reactant_filter_smarts
if product_filter_smarts is not None and product_filter_smarts.strip() != '':
r.product_filter_smarts = product_filter_smarts
r.save()
return r
@property
def url(self):
return '{}/simple-ambit-rule/{}'.format(self.package.url, self.uuid)
@ -642,7 +673,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin):
def create(package: Package, name: str = None, description: str = None,
educts: Union[List[str], List[CompoundStructure]] = None,
products: Union[List[str], List[CompoundStructure]] = None,
rule: Rule = None, multi_step: bool = True):
rules: Union[Rule|List[Rule]] = None, multi_step: bool = True):
_educts = []
_products = []
@ -662,17 +693,60 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin):
_products += products
else:
raise ValueError("")
raise ValueError("Found mixed types for educts and/or products!")
if len(_educts) == 0 or len(_products) == 0:
raise ValueError("No educts or products specified!")
if rules is None:
rules = []
if isinstance(rules, Rule):
rules = [rules]
query = Reaction.objects.annotate(
educt_count=Count('educts', filter=Q(educts__in=_educts), distinct=True),
product_count=Count('products', filter=Q(products__in=_products), distinct=True),
)
# The annotate/filter wont work if rules is an empty list
if rules:
query = query.annotate(
rule_count=Count('rules', filter=Q(rules__in=rules), distinct=True)
).filter(rule_count=len(rules))
else:
query = query.annotate(
rule_count=Count('rules', distinct=True)
).filter(rule_count=0)
existing_reaction_qs = query.filter(
educt_count=len(_educts),
product_count=len(_products),
multi_step=multi_step,
package=package
)
if existing_reaction_qs.exists():
if existing_reaction_qs.count() > 1:
logger.error(f'Found more than one reaction for given input! {existing_reaction_qs}')
return existing_reaction_qs.first()
r = Reaction()
r.package = package
if name is not None and name.strip() != '':
r.name = name
if description is not None and name.strip() != '':
r.description = description
r.multi_step = multi_step
r.save()
if rule:
if rules:
for rule in rules:
r.rules.add(rule)
for educt in _educts:
@ -700,6 +774,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin):
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey('epdb.Package', verbose_name='Package', on_delete=models.CASCADE, db_index=True)
setting = models.ForeignKey('epdb.Setting', verbose_name='Setting', on_delete=models.CASCADE, null=True, blank=True)
@property
def root_nodes(self):
@ -709,6 +784,12 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
def nodes(self):
return Node.objects.filter(pathway=self)
def get_node(self, node_url):
for n in self.nodes:
if n.url == node_url:
return n
return None
@property
def edges(self):
return Edge.objects.filter(pathway=self)
@ -717,6 +798,26 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
def url(self):
return '{}/pathway/{}'.format(self.package.url, self.uuid)
# Mode
def is_built(self):
return self.kv.get('mode', 'build') == 'predicted'
def is_predicted(self):
return self.kv.get('mode', 'build') == 'predicted'
def is_predicted(self):
return self.kv.get('mode', 'build') == 'predicted'
# Status
def completed(self):
return self.kv.get('status', 'completed') == 'completed'
def running(self):
return self.kv.get('status', 'completed') == 'running'
def failed(self):
return self.kv.get('status', 'completed') == 'failed'
def d3_json(self):
# Ideally it would be something like this but
# to reduce crossing in edges do a DFS
@ -770,7 +871,11 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
new_link = {
'name': link['name'],
'id': link['id'],
'url': link['url'],
'image': link['image'],
'reaction': link['reaction'],
'reaction_probability': link['reaction_probability'],
'scenarios': link['scenarios'],
'source': node_url_to_idx[link['start_node_urls'][0]],
'target': pseudo_idx
}
@ -781,7 +886,11 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
new_link = {
'name': link['name'],
'id': link['id'],
'url': link['url'],
'image': link['image'],
'reaction': link['reaction'],
'reaction_probability': link['reaction_probability'],
'scenarios': link['scenarios'],
'source': pseudo_idx,
'target': node_url_to_idx[target]
}
@ -797,9 +906,9 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
"completed": "true",
"description": self.description,
"id": self.url,
"isIncremental": False,
"isPredicted": False,
"lastModified": 1447842835894,
"isIncremental": self.kv.get('mode') == 'incremental',
"isPredicted": self.kv.get('mode') == 'predicted',
"lastModified": self.modified.strftime('%Y-%m-%d %H:%M:%S'),
"pathwayName": self.name,
"reviewStatus": "reviewed" if self.package.reviewed else 'unreviewed',
"scenarios": [],
@ -813,18 +922,38 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
@staticmethod
@transaction.atomic
def create(package, name, description, smiles):
def create(package: 'Package', smiles: str, name: Optional[str] = None, description: Optional[str] = None):
pw = Pathway()
pw.package = package
pw.name = name
pw.description = description
pw.save()
if name is None:
name = f"Pathway {Pathway.objects.filter(package=package).count() + 1}"
pw.name = name
if description is not None:
pw.description = description
pw.save()
try:
# create root node
Node.create(pw, smiles, 0)
except ValueError as e:
# Node creation failed, most likely due to an invalid smiles
# delete this pathway...
pw.delete()
raise e
return pw
@transaction.atomic
def add_node(self, smiles: str, name: Optional[str] = None, description: Optional[str] = None):
return Node.create(self, smiles, 0)
@transaction.atomic
def add_edge(self, start_nodes: List['Node'], end_nodes: List['Node'], rule: Optional['Rule'] = None,
name: Optional[str] = None, description: Optional[str] = None):
return Edge.create(self, start_nodes, end_nodes, rule, name=name, description=description)
class Node(EnviPathModel, AliasMixin, ScenarioMixin):
pathway = models.ForeignKey('epdb.Pathway', verbose_name='belongs to', on_delete=models.CASCADE, db_index=True)
@ -848,14 +977,14 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin):
"imageSize": 490, # TODO
"name": self.default_node_label.name,
"smiles": self.default_node_label.smiles,
"scenarios": [{'name': s.name, 'url': s.url} for s in self.scenarios.all()],
}
@staticmethod
def create(pathway, smiles, depth):
c = Compound.create(pathway.package, smiles)
def create(pathway: 'Pathway', smiles: str, depth: int, name: Optional[str] = None, description: Optional[str] = None):
c = Compound.create(pathway.package, smiles, name=name, description=description)
if Node.objects.filter(pathway=pathway, default_node_label=c.default_structure).exists():
print("found node")
return Node.objects.get(pathway=pathway, default_node_label=c.default_structure)
n = Node()
@ -886,34 +1015,21 @@ class Edge(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
return '{}/edge/{}'.format(self.pathway.url, self.uuid)
def d3_json(self):
# {
# "ecNumbers": [
# {
# "ecName": "DDT 2,3-dioxygenase",
# "ecNumber": "1.14.12.-"
# }
# ],
# "id": "https://envipath.org/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1/pathway/3f58e4d4-1c63-4b30-bf31-7ae4b98899fe/edge/ff193e7b-f010-43d4-acb3-45f34d938824",
# "idreaction": "https://envipath.org/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1/reaction/e11419cd-6b46-470b-8a06-a08d62281734",
# "multistep": "false",
# "name": "Eawag BBD reaction r0450",
# "pseudo": False,
# "scenarios": [],
# "source": 0,
# "target": 4
# }
return {
'name': self.name,
'id': self.url,
'reaction': self.edge_label.url if self.edge_label else None,
'url': self.url,
'image': self.url + '?image=svg',
'reaction': {'name': self.edge_label.name, 'url': self.edge_label.url } if self.edge_label else None,
'reaction_probability': self.kv.get('probability'),
# TODO
'start_node_urls': [x.url for x in self.start_nodes.all()],
'end_node_urls': [x.url for x in self.end_nodes.all()],
"scenarios": [{'name': s.name, 'url': s.url} for s in self.scenarios.all()],
}
@staticmethod
def create(pathway, start_nodes, end_nodes, rule: Optional[Rule] = None, name: Optional[str] = None,
def create(pathway, start_nodes: List[Node], end_nodes: List[Node], rule: Optional[Rule] = None, name: Optional[str] = None,
description: Optional[str] = None):
e = Edge()
e.pathway = pathway
@ -934,13 +1050,17 @@ class Edge(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
r = Reaction.create(pathway.package, name=name, description=description,
educts=[n.default_node_label for n in e.start_nodes.all()],
products=[n.default_node_label for n in e.end_nodes.all()],
rule=rule, multi_step=False
rules=rule, multi_step=False
)
e.edge_label = r
e.save()
return e
@property
def as_svg(self):
return self.edge_label.as_svg if self.edge_label else None
class EPModel(PolymorphicModel, EnviPathModel):
package = models.ForeignKey('epdb.Package', verbose_name='Package', on_delete=models.CASCADE, db_index=True)
@ -976,6 +1096,9 @@ class MLRelativeReasoning(EPModel):
eval_results = JSONField(null=True, blank=True, default=dict)
def status(self):
return self.PROGRESS_STATUS_CHOICES[self.model_status]
@staticmethod
@transaction.atomic
def create(package, name, description, rule_packages, data_packages, eval_packages, threshold):
@ -1201,7 +1324,7 @@ class MLRelativeReasoning(EPModel):
self.save()
mod = SparseLabelECC(
**s.DEFAULT_MODELS_PARAMS
**s.DEFAULT_DT_MODEL_PARAMS
)
mod.fit(X, y)
@ -1247,7 +1370,7 @@ class MLRelativeReasoning(EPModel):
y_train, y_test = y[train_index], y[test_index]
model = SparseLabelECC(
**s.DEFAULT_MODELS_PARAMS
**s.DEFAULT_DT_MODEL_PARAMS
)
model.fit(X_train, y_train)
@ -1500,11 +1623,15 @@ class Setting(EnviPathModel):
max_nodes = models.IntegerField(null=False, blank=False, verbose_name='Setting Max Number of Nodes', default=30)
rule_packages = models.ManyToManyField("Package", verbose_name="Setting Rule Packages",
related_name="setting_rule_packages")
related_name="setting_rule_packages", blank=True)
model = models.ForeignKey('EPModel', verbose_name='Setting EPModel', on_delete=models.SET_NULL, null=True,
blank=True)
model_threshold = models.FloatField(null=True, blank=True, verbose_name='Setting Model Threshold', default=0.25)
@property
def url(self):
return '{}/setting/{}'.format(s.SERVER_URL, self.uuid)
@cached_property
def applicable_rules(self):
"""

View File

@ -1,4 +1,6 @@
import logging
from typing import Optional
from celery.signals import worker_process_init
from celery import shared_task
from epdb.models import Pathway, Node, Edge, EPModel, Setting
@ -40,11 +42,40 @@ def evaluate_model(model_pk: int):
@shared_task(queue='predict')
def predict(pw_pk: int, pred_setting_pk: int):
def predict(pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_pk: Optional[int] = None) -> Pathway:
pw = Pathway.objects.get(id=pw_pk)
setting = Setting.objects.get(id=pred_setting_pk)
pw.kv.update(**{'status': 'running'})
pw.save()
try:
# regular prediction
if limit is not None:
spw = SPathway(prediction_setting=setting, persist=pw)
level = 0
while not spw.done:
spw.predict_step(from_depth=level)
level += 1
# break in case we are in incremental model
if limit != -1:
if level >= limit:
break
elif node_pk is not None:
n = Node.objects.get(id=node_pk, pathway=pw)
spw = SPathway.from_pathway(pw)
spw.predict_step(from_node=n)
else:
raise ValueError("Neither limit nor node_pk given!")
except Exception as e:
pw.kv.update({'status': 'failed'})
pw.save()
raise e
pw.kv.update(**{'status': 'completed'})
pw.save()

View File

View File

@ -0,0 +1,7 @@
from django import template
register = template.Library()
@register.filter
def classname(obj):
return obj.__class__.__name__

View File

@ -13,6 +13,8 @@ urlpatterns = [
# Home
re_path(r'^$', v.index, name='index'),
# re_path(r'^login', v.login, name='login'),
# Top level urls
re_path(r'^package$', v.packages, name='packages'),
re_path(r'^compound$', v.compounds, name='compounds'),
@ -53,11 +55,11 @@ urlpatterns = [
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway$', v.package_pathways, name='package pathway list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})$', v.package_pathway, name='package pathway detail'),
# Pathway Nodes
# re_path(rf'^package/(?P<package_uuid>{UUID})/pathway(?P<pathway_uuid>{UUID})/node$', v.package_pathway_nodes, name='package pathway node list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$', v.package_pathway_nodes, name='package pathway node list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node/(?P<node_uuid>{UUID})$', v.package_pathway_node, name='package pathway node detail'),
# Pathway Edges
# re_path(rf'^package/(?P<package_uuid>{UUID})/pathway(?P<pathway_uuid>{UUID})/edge$', v.package_pathway_edges, name='package pathway edge list'),
# re_path(rf'^package/(?P<package_uuid>{UUID})/pathway(?P<pathway_uuid>{UUID})/edge/(?P<edge_uuid>{UUID})$', v.package_pathway_edge, name='package pathway edge detail'),
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge$', v.package_pathway_edges, name='package pathway edge list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge/(?P<edge_uuid>{UUID})$', v.package_pathway_edge, name='package pathway edge detail'),
# Scenario
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario$', v.package_scenarios, name='package scenario list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario/(?P<scenario_uuid>{UUID})$', v.package_scenario, name='package scenario detail'),

View File

@ -1,5 +1,6 @@
import json
import logging
from functools import wraps
from typing import List, Dict, Any
from django.conf import settings as s
@ -7,29 +8,71 @@ from django.contrib.auth import get_user_model
from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from utilities.chem import FormatConverter, IndigoUtils
from .logic import GroupManager, PackageManager, UserManager, SettingManager
from .logic import GroupManager, PackageManager, UserManager, SettingManager, SearchManager
from .models import Package, GroupPackagePermission, Group, CompoundStructure, Compound, Reaction, Rule, Pathway, Node, \
EPModel, EnviFormer, MLRelativeReasoning, RuleBaseRelativeReasoning, Scenario, SimpleAmbitRule, APIToken, \
UserPackagePermission, Permission, License, User
UserPackagePermission, Permission, License, User, Edge
logger = logging.getLogger(__name__)
def log_post_params(request):
if s.DEBUG:
for k, v in request.POST.items():
logger.debug(f"{k}\t{v}")
def catch_exceptions(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
try:
return view_func(request, *args, **kwargs)
except Exception as e:
# Optionally return JSON or plain HttpResponse
if request.headers.get('Accept') == 'application/json':
return JsonResponse(
{'error': 'Internal server error. Please try again later.'},
status=500
)
else:
return render(request, 'errors/error.html', get_base_context(request))
return _wrapped_view
def editable(request, user):
url = request.build_absolute_uri(request.path)
if PackageManager.is_package_url(url):
_package = PackageManager.get_package_lp(request.build_absolute_uri())
return PackageManager.writable(user, _package)
elif GroupManager.is_group_url(url):
_group = GroupManager.get_group_lp(request.build_absolute_uri())
return GroupManager.writable(user, _group)
elif UserManager.is_user_url(url):
_user = UserManager.get_user_lp(request.build_absolute_uri())
return UserManager.writable(user, _user)
elif url in [s.SERVER_URL, f"{s.SERVER_URL}/", f"{s.SERVER_URL}/package", f"{s.SERVER_URL}/user",
f"{s.SERVER_URL}/group", f"{s.SERVER_URL}/search"]:
return True
else:
print(f"Unknown url: {url}")
return False
def get_base_context(request) -> Dict[str, Any]:
current_user = _anonymous_or_real(request)
can_edit = editable(request, current_user)
ctx = {
'title': 'enviPath',
'meta': {
'version': '0.0.1',
'server_url': s.SERVER_URL,
'user': current_user,
'can_edit': can_edit,
'readable_packages': PackageManager.get_all_readable_packages(current_user, include_reviewed=True),
'writeable_packages': PackageManager.get_all_writeable_packages(current_user),
'available_groups': GroupManager.get_groups(current_user),
@ -65,8 +108,9 @@ def breadcrumbs(first_level_object=None, second_level_namespace=None, second_lev
return bread
# @catch_exceptions
def index(request):
current_user = _anonymous_or_real(request)
context = get_base_context(request)
context['title'] = 'enviPath - Home'
context['meta']['current_package'] = context['meta']['user'].default_package
@ -77,6 +121,16 @@ def index(request):
return render(request, 'index/index.html', context)
# def login(request):
# current_user = _anonymous_or_real(request)
# if request.method == 'GET':
# context = get_base_context(request)
# context['title'] = 'enviPath'
# return render(request, 'login.html', context)
# else:
# return HttpResponseBadRequest()
#
# @login_required(login_url='/login')
def packages(request):
current_user = _anonymous_or_real(request)
@ -86,9 +140,10 @@ def packages(request):
context['object_type'] = 'package'
context['meta']['current_package'] = context['meta']['user'].default_package
context['meta']['can_edit'] = True
reviewed_package_qs = Package.objects.filter(reviewed=True)
unreviewed_package_qs = PackageManager.get_all_readable_packages(current_user)
reviewed_package_qs = Package.objects.filter(reviewed=True).order_by('created')
unreviewed_package_qs = PackageManager.get_all_readable_packages(current_user).order_by('name')
context['reviewed_objects'] = reviewed_package_qs
context['unreviewed_objects'] = unreviewed_package_qs
@ -103,7 +158,6 @@ def packages(request):
else:
package_name = request.POST.get('package-name')
package_description = request.POST.get('package-description', s.DEFAULT_VALUES['description'])
# group = GroupManager.get_group_by_url(request.user, request.POST.get('package-group'))
created_package = PackageManager.create_package(current_user, package_name, package_description)
@ -342,7 +396,23 @@ def models(request):
def search(request):
current_user = _anonymous_or_real(request)
if request.method == 'GET':
package_urls = request.GET.getlist('packages')
searchterm = request.GET.get('search')
mode = request.GET.get('mode')
# add HTTP_ACCEPT check to differentiate between index and ajax call
if 'application/json' in request.META.get('HTTP_ACCEPT') and all([searchterm, mode]):
if package_urls:
packages = [PackageManager.get_package_by_url(current_user, p) for p in package_urls]
else:
packages = PackageManager.get_reviewed_packages()
search_result = SearchManager.search(packages, searchterm, mode)
return JsonResponse(search_result, safe=False)
context = get_base_context(request)
context['title'] = 'enviPath - Search'
@ -352,13 +422,21 @@ def search(request):
{'Home': s.SERVER_URL},
{'Search': s.SERVER_URL + '/search'},
]
# TODO perm
reviewed_package_qs = Package.objects.filter(reviewed=True)
unreviewed_package_qs = Package.objects.filter(reviewed=False)
reviewed_package_qs = PackageManager.get_reviewed_packages()
unreviewed_package_qs = PackageManager.get_all_readable_packages(current_user)
context['reviewed_objects'] = reviewed_package_qs
context['unreviewed_objects'] = unreviewed_package_qs
if all([searchterm, mode]):
if package_urls:
packages = [PackageManager.get_package_by_url(current_user, p) for p in package_urls]
else:
packages = PackageManager.get_reviewed_packages()
context['search_result'] = SearchManager.search(packages, searchterm, mode)
return render(request, 'search.html', context)
@ -487,7 +565,7 @@ def package_model(request, package_uuid, model_uuid):
return render(request, 'objects/model.html', context)
if request.method == 'POST':
elif request.method == 'POST':
if hidden := request.POST.get('hidden', None):
if hidden == 'delete-model':
current_model.delete()
@ -496,21 +574,9 @@ def package_model(request, package_uuid, model_uuid):
return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
#
# new_compound_name = request.POST.get('compound-name')
# new_compound_description = request.POST.get('compound-description')
#
# if new_compound_name:
# current_compound.name = new_compound_name
#
# if new_compound_description:
# current_compound.description = new_compound_description
#
# if any([new_compound_name, new_compound_description]):
# current_compound.save()
# return redirect(current_compound.url)
# else:
# return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
def package(request, package_uuid):
@ -803,8 +869,7 @@ def package_rules(request, package_uuid):
elif request.method == 'POST':
for k, v in request.POST.items():
print(k, v)
log_post_params(request)
# Generic params
rule_name = request.POST.get('rule-name')
@ -817,8 +882,8 @@ def package_rules(request, package_uuid):
# Obtain parameters as required by rule type
if rule_type == 'SimpleAmbitRule':
params['smirks'] = request.POST.get('rule-smirks')
params['reactant_smarts'] = request.POST.get('rule-reactant-smarts')
params['product_smarts'] = request.POST.get('rule-product-smarts')
params['reactant_filter_smarts'] = request.POST.get('rule-reactant-smarts')
params['product_filter_smarts'] = request.POST.get('rule-product-smarts')
elif rule_type == 'SimpleRDKitRule':
params['reaction_smarts'] = request.POST.get('rule-reaction-smarts')
elif rule_type == 'ParallelRule':
@ -828,7 +893,7 @@ def package_rules(request, package_uuid):
else:
return HttpResponseBadRequest()
r = Rule.create(current_package, rule_type, name=rule_name, description=rule_description, **params)
r = Rule.create(rule_type=rule_type, package=current_package, name=rule_name, description=rule_description, **params)
return redirect(r.url)
else:
@ -854,7 +919,7 @@ def package_rule(request, package_uuid, rule_uuid):
else: # isinstance(current_rule, ParallelRule) or isinstance(current_rule, SequentialRule):
return render(request, 'objects/composite_rule.html', context)
if request.method == 'POST':
elif request.method == 'POST':
if hidden := request.POST.get('hidden', None):
if hidden == 'delete-rule':
current_rule.delete()
@ -862,8 +927,23 @@ def package_rule(request, package_uuid, rule_uuid):
else:
return HttpResponseBadRequest()
# TODO update!
rule_name = request.POST.get('rule-name', '').strip()
rule_description = request.POST.get('rule-description', '').strip()
if rule_name:
current_rule.name = rule_name
if rule_description:
current_rule.description = rule_description
if any([rule_name, rule_description]):
current_rule.save()
return redirect(current_rule.url)
else:
return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/reaction
def package_reactions(request, package_uuid):
@ -912,6 +992,8 @@ def package_reactions(request, package_uuid):
return redirect(r.url)
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/reaction/<id>
def package_reaction(request, package_uuid, reaction_uuid):
@ -931,7 +1013,7 @@ def package_reaction(request, package_uuid, reaction_uuid):
return render(request, 'objects/reaction.html', context)
if request.method == 'POST':
elif request.method == 'POST':
if hidden := request.POST.get('hidden', None):
if hidden == 'delete-reaction':
current_reaction.delete()
@ -954,6 +1036,8 @@ def package_reaction(request, package_uuid, reaction_uuid):
else:
return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway
def package_pathways(request, package_uuid):
@ -989,7 +1073,7 @@ def package_pathways(request, package_uuid):
return render(request, 'collections/objects_list.html', context)
if request.method == 'POST':
elif request.method == 'POST':
log_post_params(request)
@ -1002,15 +1086,34 @@ def package_pathways(request, package_uuid):
return HttpResponseBadRequest()
stand_smiles = FormatConverter.standardize(smiles)
pw = Pathway.create(current_package, name, description, stand_smiles)
if pw_mode != 'build':
if pw_mode not in ['predict', 'build', 'incremental']:
return HttpResponseBadRequest()
pw = Pathway.create(current_package, name, description, stand_smiles)
# set mode
pw.kv.update({'mode': pw_mode})
pw.save()
if pw_mode == 'predict' or pw_mode == 'incremental':
# unlimited pred (will be handled by setting)
limit = -1
# For incremental predict first level and return
if pw_mode == 'incremental':
limit = 1
pred_setting = current_user.prediction_settings()
pw.setting = pred_setting
pw.save()
from .tasks import predict
predict.delay(pw.pk, pred_setting.pk)
predict.delay(pw.pk, pred_setting.pk, limit=limit)
return redirect(pw.url)
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway/<id>
def package_pathway(request, package_uuid, pathway_uuid):
@ -1043,7 +1146,7 @@ def package_pathway(request, package_uuid, pathway_uuid):
return render(request, 'objects/pathway.html', context)
# return render(request, 'pathway_playground2.html', context)
if request.method == 'POST':
elif request.method == 'POST':
if hidden := request.POST.get('hidden', None):
if hidden == 'delete-pathway':
current_pathway.delete()
@ -1051,30 +1154,92 @@ def package_pathway(request, package_uuid, pathway_uuid):
else:
return HttpResponseBadRequest()
pathway_name = request.POST.get('pathway-name')
pathway_description = request.POST.get('pathway-description')
#
#
#
# def package_relative_reasonings(request, package_id):
# if request.method == 'GET':
# pass
#
#
# def package_relative_reasoning(request, package_id, relative_reasoning_id):
# current_user = _anonymous_or_real(request)
#
# if request.method == 'GET':
# pass
# elif request.method == 'POST':
# pass
#
# #
# #
# # # https://envipath.org/package/<id>/pathway/<id>/node
# # def package_pathway_nodes(request, package_id, pathway_id):
# # pass
# #
# #
if any([pathway_name, pathway_description]):
if pathway_name is not None and pathway_name.strip() != '':
pathway_name = pathway_name.strip()
current_pathway.name = pathway_name
if pathway_description is not None and pathway_description.strip() != '':
pathway_description = pathway_description.strip()
current_pathway.description = pathway_description
current_pathway.save()
return redirect(current_pathway.url)
node_url = request.POST.get('node')
if node_url:
n = current_pathway.get_node(node_url)
from .tasks import predict
# Dont delay?
predict(current_pathway.pk, current_pathway.setting.pk, node_pk=n.pk)
return JsonResponse({'success': current_pathway.url})
return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway/<id>/node
def package_pathway_nodes(request, package_uuid, pathway_uuid):
current_user = _anonymous_or_real(request)
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
current_pathway = Pathway.objects.get(package=current_package, uuid=pathway_uuid)
if request.method == 'GET':
context = get_base_context(request)
context['title'] = f'enviPath - {current_package.name} - {current_pathway.name} - Nodes'
context['meta']['current_package'] = current_package
context['object_type'] = 'node'
context['breadcrumbs'] = [
{'Home': s.SERVER_URL},
{'Package': s.SERVER_URL + '/package'},
{current_package.name: current_package.url},
{'Pathway': current_package.url + '/pathway'},
{current_pathway.name: current_pathway.url},
{'Node': current_pathway.url + '/node'},
]
reviewed_node_qs = Node.objects.none()
unreviewed_node_qs = Node.objects.none()
if current_package.reviewed:
reviewed_node_qs = Node.objects.filter(pathway=current_pathway).order_by('name')
else:
unreviewed_node_qs = Node.objects.filter(pathway=current_pathway).order_by('name')
if request.GET.get('all'):
return JsonResponse({
"objects": [
{"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
for pw in (reviewed_node_qs if current_package.reviewed else unreviewed_node_qs)
]
})
context['reviewed_objects'] = reviewed_node_qs
context['unreviewed_objects'] = unreviewed_node_qs
return render(request, 'collections/objects_list.html', context)
elif request.method == 'POST':
node_name = request.POST.get('node-name')
node_description = request.POST.get('node-description')
node_smiles = request.POST.get('node-smiles')
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
return redirect(current_pathway.url)
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway/<id>/node/<id>
@ -1091,19 +1256,129 @@ def package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
svg_data = current_node.as_svg
return HttpResponse(svg_data, content_type="image/svg+xml")
context = get_base_context(request)
context['title'] = f'enviPath - {current_package.name} - {current_pathway.name}'
context['meta']['current_package'] = current_package
context['object_type'] = 'pathway'
context['breadcrumbs'] = [
{'Home': s.SERVER_URL},
{'Package': s.SERVER_URL + '/package'},
{current_package.name: current_package.url},
{'Pathway': current_package.url + '/pathway'},
{current_pathway.name: current_pathway.url},
{'Node': current_pathway.url + '/node'},
{current_node.name: current_node.url},
]
context['node'] = current_node
return render(request, 'objects/node.html', context)
elif request.method == 'POST':
if s.DEBUG:
for k, v in request.POST.items():
print(k, v)
if hidden := request.POST.get('hidden', None):
if hidden == 'delete-node':
current_node.delete()
return redirect(current_pathway.url)
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway/<id>/edge
def package_pathway_edges(request, package_uuid, pathway_uuid):
current_user = _anonymous_or_real(request)
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
current_pathway = Pathway.objects.get(package=current_package, uuid=pathway_uuid)
if request.method == 'GET':
context = get_base_context(request)
context['title'] = f'enviPath - {current_package.name} - {current_pathway.name} - Edges'
context['meta']['current_package'] = current_package
context['object_type'] = 'edge'
context['breadcrumbs'] = [
{'Home': s.SERVER_URL},
{'Package': s.SERVER_URL + '/package'},
{current_package.name: current_package.url},
{'Pathway': current_package.url + '/pathway'},
{current_pathway.name: current_pathway.url},
{'Edge': current_pathway.url + '/edge'},
]
reviewed_edge_qs = Edge.objects.none()
unreviewed_edge_qs = Edge.objects.none()
if current_package.reviewed:
reviewed_edge_qs = Edge.objects.filter(pathway=current_pathway).order_by('name')
else:
unreviewed_edge_qs = Edge.objects.filter(pathway=current_pathway).order_by('name')
if request.GET.get('all'):
return JsonResponse({
"objects": [
{"name": pw.name, "url": pw.url, "reviewed": current_package.reviewed}
for pw in (reviewed_edge_qs if current_package.reviewed else unreviewed_edge_qs)
]
})
context['reviewed_objects'] = reviewed_edge_qs
context['unreviewed_objects'] = unreviewed_edge_qs
return render(request, 'collections/objects_list.html', context)
elif request.method == 'POST':
edge_name = request.POST.get('edge-name')
edge_description = request.POST.get('edge-description')
edge_substrates = request.POST.getlist('edge-substrates')
edge_products = request.POST.getlist('edge-products')
substrate_nodes = [current_pathway.get_node(url) for url in edge_substrates]
product_nodes = [current_pathway.get_node(url) for url in edge_products]
# TODO in the future consider Rules here?
current_pathway.add_edge(substrate_nodes, product_nodes, name=edge_name, description=edge_description)
return redirect(current_pathway.url)
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway/<id>/edge/<id>
def package_pathway_edge(request, package_uuid, pathway_uuid, edge_uuid):
current_user = _anonymous_or_real(request)
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
current_pathway = Pathway.objects.get(package=current_package, uuid=pathway_uuid)
current_edge = Edge.objects.get(pathway=current_pathway, uuid=edge_uuid)
if request.method == 'GET':
is_image_request = request.GET.get('image')
if is_image_request:
if is_image_request == 'svg':
svg_data = current_edge.as_svg
return HttpResponse(svg_data, content_type="image/svg+xml")
elif request.method == 'POST':
if s.DEBUG:
for k, v in request.POST.items():
print(k, v)
if hidden := request.POST.get('hidden', None):
if hidden == 'delete-edge':
current_edge.delete()
return redirect(current_pathway.url)
else:
return HttpResponseBadRequest()
# #
# #
# # # https://envipath.org/package/<id>/pathway/<id>/edge
# # def package_pathway_edges(request, package_id, pathway_id):
# # pass
# #
# #
# # # https://envipath.org/package/<id>/pathway/<id>/edge/<id>
# # def package_pathway_edge(request, package_id, pathway_id, edge_id):
# # pass
# #
# #
# https://envipath.org/package/<id>/scenario
def package_scenarios(request, package_uuid):
current_user = _anonymous_or_real(request)
@ -1158,11 +1433,6 @@ def package_scenario(request, package_uuid, scenario_uuid):
return render(request, 'objects/scenario.html', context)
### END UNTESTED
##############
# User/Group #
##############
@ -1201,7 +1471,7 @@ def users(request):
return render(request, 'errors/user_account_inactive.html', status=403)
email = temp_user.email
except get_user_model().DoesNotExists:
except get_user_model().DoesNotExist:
return HttpResponseBadRequest()
user = authenticate(username=email, password=password)
@ -1289,6 +1559,18 @@ def user(request, user_uuid):
logout(request)
return redirect(s.SERVER_URL)
default_package = request.POST.get('default-package')
default_group = request.POST.get('default-group')
default_prediction_setting = request.POST.get('default-prediction-setting')
if any([default_package, default_group, default_prediction_setting]):
current_user.default_package = PackageManager.get_package_by_url(current_user, default_package)
current_user.default_group = GroupManager.get_group_by_url(current_user, default_group)
current_user.default_setting = SettingManager.get_setting_by_url(current_user, default_prediction_setting)
current_user.save()
return redirect(current_user.url)
prediction_model_pk = request.POST.get('model')
prediction_threshold = request.POST.get('threshold')
prediction_max_nodes = request.POST.get('max_nodes')
@ -1509,3 +1791,9 @@ def layout(request):
def depict(request):
if smiles := request.GET.get('smiles'):
return HttpResponse(IndigoUtils.mol_to_svg(smiles), content_type='image/svg+xml')
elif smirks := request.GET.get('smirks'):
query_smirks = request.GET.get('is_query_smirks', False) == 'true'
return HttpResponse(IndigoUtils.smirks_to_svg(smirks, query_smirks), content_type='image/svg+xml')
else:
return HttpResponseBadRequest()

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -507,28 +507,6 @@ function makeAccordionPanel(accordionId, panelName, panelContent, collapsed, id)
+ "</div>";
}
function makeSearchList(divToAppend, jsonob) {
if(jsonob.status){
$(divToAppend).append('<div class="alert alert-danger" role="alert"><p>'+"No results..."+'</p></div>');
return;
}
var content = makeAccordionHead("searchAccordion", "Results","");
for ( var type in jsonob){
var obj = jsonob[type];
var objs = "";
for ( var x in obj) {
objs += "<a class='list-group-item' href=\"" + obj[x].id + "\">"
+ obj[x].name + "</a>";
}
content += makeAccordionPanel("searchAccordion", type, objs, true);
}
$(divToAppend).append(content);
}
function fillPRCurve(modelUri, onclick){
if (modelUri == '') {
return;

View File

@ -1,5 +1,18 @@
console.log("loaded")
function predictFromNode(url) {
$.post("", {node: url})
.done(function (data) {
console.log("Success:", data);
window.location.href = data.success;
})
.fail(function (xhr, status, error) {
console.error("Error:", xhr.status, xhr.responseText);
// show user-friendly message or log error
});
}
// data = {{ pathway.d3_json | safe }};
// elem = 'vizdiv'
function draw(pathway, elem) {
@ -77,8 +90,100 @@ function draw(pathway, elem) {
d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted"));
}
// Wait one second before showing popup
var popupWaitBeforeShow = 1000;
// Keep Popup at least for one second
var popushowAtLeast = 1000;
const tooltip = d3.select("#tooltip");
function pop_show_e(element) {
var e = element;
setTimeout(function () {
if ($(e).is(':hover')) { // if element is still hovered
$(e).popover("show");
// workaround to set fixed positions
pop = $(e).attr("aria-describedby")
h = $('#' + pop).height();
$('#' + pop).attr("style", `position: fixed; top: ${clientY - (h / 2.0)}px; left: ${clientX + 10}px; margin: 0px; max-width: 1000px; display: block;`)
setTimeout(function () {
var close = setInterval(function () {
if (!$(".popover:hover").length // mouse outside popover
&& !$(e).is(':hover')) { // mouse outside element
$(e).popover('hide');
clearInterval(close);
}
}, 100);
}, popushowAtLeast);
}
}, popupWaitBeforeShow);
}
function pop_add(objects, title, contentFunction) {
objects.attr("id", "pop")
.attr("data-container", "body")
.attr("data-toggle", "popover")
.attr("data-placement", "right")
.attr("title", title);
objects.each(function (d, i) {
options = {trigger: "manual", html: true, animation: false};
this_ = this;
var p = $(this).popover(options).on("mouseenter", function () {
pop_show_e(this);
});
p.on("show.bs.popover", function (e) {
// this is to dynamically ajdust the content and bounds of the popup
p.attr('data-content', contentFunction(d));
p.data("bs.popover").setContent();
p.data("bs.popover").tip().css({"max-width": "1000px"});
});
});
}
function node_popup(n) {
popupContent = "<a href='" + n.url +"'>" + n.name + "</a><br>";
popupContent += "Depth " + n.depth + "<br>"
popupContent += "<img src='" + n.image + "' width='"+ 20 * nodeRadius +"'><br>"
if (n.scenarios.length > 0) {
popupContent += '<b>Half-lives and related scenarios:</b><br>'
for (var s of n.scenarios) {
popupContent += "<a href='" + s.url + "'>" + s.name + "</a><br>";
}
}
var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0;
if(pathway.isIncremental && isLeaf) {
popupContent += '<br><a class="btn btn-primary" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
}
return popupContent;
}
function edge_popup(e) {
popupContent = "<a href='" + e.url +"'>" + e.name + "</a><br>";
popupContent += "<img src='" + e.image + "' width='"+ 20 * nodeRadius +"'><br>"
if (e.reaction_probability) {
popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>';
}
if (e.scenarios.length > 0) {
popupContent += '<b>Half-lives and related scenarios:</b><br>'
for (var s of e.scenarios) {
popupContent += "<a href='" + s.url + "'>" + s.name + "</a><br>";
}
}
return popupContent;
}
var clientX;
var clientY;
document.addEventListener('mousemove', function(event) {
clientX = event.clientX;
clientY =event.clientY;
});
const zoomable = d3.select("#zoomable");
@ -128,13 +233,15 @@ function draw(pathway, elem) {
.enter().append("line")
// Check if target is pseudo and draw marker only if not pseudo
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
.on("mouseover", (event, d) => {
tooltip.style("visibility", "visible")
.text(`Link: ${d.source.id}${d.target.id}`)
.style("top", `${event.pageY + 5}px`)
.style("left", `${event.pageX + 5}px`);
})
.on("mouseout", () => tooltip.style("visibility", "hidden"));
// .on("mouseover", (event, d) => {
// tooltip.style("visibility", "visible")
// .text(`Link: ${d.source.id} → ${d.target.id}`)
// .style("top", `${event.pageY + 5}px`)
// .style("left", `${event.pageX + 5}px`);
// })
// .on("mouseout", () => tooltip.style("visibility", "hidden"));
pop_add(link, "Reaction", edge_popup);
// Knoten zeichnen
const node = zoomable.append("g")
@ -148,16 +255,16 @@ function draw(pathway, elem) {
.on("click", function (event, d) {
d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
})
.on("mouseover", (event, d) => {
if (d.pseudo) {
return
}
tooltip.style("visibility", "visible")
.text(`Node: ${d.id} Depth: ${d.depth}`)
.style("top", `${event.pageY + 5}px`)
.style("left", `${event.pageX + 5}px`);
})
.on("mouseout", () => tooltip.style("visibility", "hidden"));
// .on("mouseover", (event, d) => {
// if (d.pseudo) {
// return
// }
// tooltip.style("visibility", "visible")
// .text(`Node: ${d.id} Depth: ${d.depth}`)
// .style("top", `${event.pageY + 5}px`)
// .style("left", `${event.pageX + 5}px`);
// })
// .on("mouseout", () => tooltip.style("visibility", "hidden"));
// Kreise für die Knoten hinzufügen
node.append("circle")
@ -172,4 +279,6 @@ function draw(pathway, elem) {
.attr("y", -nodeRadius)
.attr("width", nodeRadius * 2)
.attr("height", nodeRadius * 2);
pop_add(node, "Compound", node_popup);
}

View File

@ -1,4 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#new_compound_modal">
<span class="glyphicon glyphicon-plus"></span> New Compound</a>
</li>
{% endif %}

View File

@ -1,4 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#new_compound_structure_modal">
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a>
</li>
{% endif %}

View File

@ -0,0 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#new_edge_modal">
<span class="glyphicon glyphicon-plus"></span> New Edge</a>
</li>
{% endif %}

View File

@ -1,4 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#new_model_modal">
<span class="glyphicon glyphicon-plus"></span> New Model</a>
</li>
{% endif %}

View File

@ -0,0 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#new_node_modal">
<span class="glyphicon glyphicon-plus"></span> New Node</a>
</li>
{% endif %}

View File

@ -1,4 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#predict_modal">
<span class="glyphicon glyphicon-plus"></span> New Pathway</a>
</li>
{% endif %}

View File

@ -1,4 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#new_reaction_modal">
<span class="glyphicon glyphicon-plus"></span> New Reaction</a>
</li>
{% endif %}

View File

@ -1,4 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#new_rule_modal">
<span class="glyphicon glyphicon-plus"></span> New Rule</a>
</li>
{% endif %}

View File

@ -1,4 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#new_pathway_modal">
<span class="glyphicon glyphicon-plus"></span> New Scenario</a>
</li>
{% endif %}

View File

@ -1,4 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#new_setting_modal">
<span class="glyphicon glyphicon-plus"></span>New Setting</a>
</li>
{% endif %}

View File

@ -1,3 +1,4 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#edit_compound_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a>
@ -10,3 +11,4 @@
<a class="button" data-toggle="modal" data-target="#delete_compound_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
</li>
{% endif %}

View File

@ -1,3 +1,4 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#edit_compound_structure_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a>
@ -6,3 +7,4 @@
<a class="button" data-toggle="modal" data-target="#delete_compound_structure_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a>
</li>
{% endif %}

View File

@ -1,3 +1,4 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#delete_group_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Group</a>
@ -6,3 +7,4 @@
<a role="button" data-toggle="modal" data-target="#edit_group_member_modal">
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a>
</li>
{% endif %}

View File

@ -0,0 +1,6 @@
{% if meta.can_edit %}
<li>
<a class="button" data-toggle="modal" data-target="#delete_model_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Model</a>
</li>
{% endif %}

View File

@ -0,0 +1,10 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#edit_node_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Node</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#delete_node_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Node</a>
</li>
{% endif %}

View File

@ -1,3 +1,4 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#edit_package_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Package</a>
@ -14,3 +15,4 @@
<a class="button" data-toggle="modal" data-target="#delete_package_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Package</a>
</li>
{% endif %}

View File

@ -1,8 +1,32 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#edit_pathway_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a>
<a class="button" data-toggle="modal" data-target="#add_pathway_node_modal">
<i class="glyphicon glyphicon-plus"></i> Add Compound</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#delete_pathawy_modal">
<a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">
<i class="glyphicon glyphicon-plus"></i> Add Reaction</a>
</li>
<li role="separator" class="divider"></li>
<li>
<a class="button" data-toggle="modal" data-target="#edit_pathway_modal">
<i class="glyphicon glyphicon-plus"></i> Edit Pathway</a>
</li>
{# <li>#}
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
{# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#}
{# </li>#}
<li role="separator" class="divider"></li>
<li>
<a class="button" data-toggle="modal" data-target="#delete_pathway_node_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#delete_pathway_edge_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#delete_pathway_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a>
</li>
{% endif %}

View File

@ -1,3 +1,4 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a>
@ -6,3 +7,4 @@
<a class="button" data-toggle="modal" data-target="#delete_reaction_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
</li>
{% endif %}

View File

@ -1,4 +1,6 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#edit_rule_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a>
</li>
{% endif %}

View File

@ -0,0 +1,2 @@
{% if meta.can_edit %}
{% endif %}

View File

@ -1,3 +1,4 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#edit_user_modal">
<i class="glyphicon glyphicon-edit"></i> Update</a>
@ -10,11 +11,12 @@
<a role="button" data-toggle="modal" data-target="#new_prediction_setting_modal">
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a>
</li>
<li>
<a role="button" data-toggle="modal" data-target="#manage_api_token_modal">
<i class="glyphicon glyphicon-console"></i> Manage API Token</a>
</li>
{# <li>#}
{# <a role="button" data-toggle="modal" data-target="#manage_api_token_modal">#}
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
{# </li>#}
<li>
<a role="button" data-toggle="modal" data-target="#delete_user_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Account</a>
</li>
{% endif %}

View File

@ -35,12 +35,16 @@
{% include "modals/collections/new_reaction_modal.html" %}
{% elif object_type == 'pathway' %}
{# {% include "modals/collections/new_pathway_modal.html" %} #}
{% elif object_type == 'node' %}
{% include "modals/collections/new_node_modal.html" %}
{% elif object_type == 'edge' %}
{% include "modals/collections/new_edge_modal.html" %}
{% elif object_type == 'scenario' %}
{% include "modals/collections/new_scenario_modal.html" %}
{% elif object_type == 'model' %}
{% include "modals/collections/new_model_modal.html" %}
{% elif object_type == 'setting' %}
{% include "modals/collections/new_setting_modal.html" %}
{#{% include "modals/collections/new_setting_modal.html" %}#}
{% elif object_type == 'user' %}
<div></div>
{% elif object_type == 'group' %}
@ -63,6 +67,10 @@
Reactions
{% elif object_type == 'pathway' %}
Pathways
{% elif object_type == 'node' %}
Nodes
{% elif object_type == 'edge' %}
Edges
{% elif object_type == 'scenario' %}
Scenarios
{% elif object_type == 'model' %}
@ -75,7 +83,7 @@
Groups
{% endif %}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
@ -100,6 +108,10 @@
{% include "actions/collections/model.html" %}
{% elif object_type == 'pathway' %}
{% include "actions/collections/pathway.html" %}
{% elif object_type == 'node' %}
{% include "actions/collections/node.html" %}
{% elif object_type == 'edge' %}
{% include "actions/collections/edge.html" %}
{% elif object_type == 'group' %}
{% include "actions/collections/group.html" %}
{% endif %}
@ -133,6 +145,14 @@
<p>A pathway displays the (predicted) biodegradation of a compound as graph.
<a target="_blank" href="https://wiki.envipath.org/index.php/pathways" role="button">Learn more
&gt;&gt;</a></p>
{% elif object_type == 'node' %}
<p>Nodes represent the (predicted) compounds in a graph.
<a target="_blank" href="https://wiki.envipath.org/index.php/nodes" role="button">Learn more
&gt;&gt;</a></p>
{% elif object_type == 'edge' %}
<p>Edges represent the links between Nodes in a graph
<a target="_blank" href="https://wiki.envipath.org/index.php/edges" role="button">Learn more
&gt;&gt;</a></p>
{% elif object_type == 'scenario' %}
<p>A scenario contains meta-information that can be attached to other data (compounds, rules, ..).
<a target="_blank" href="https://wiki.envipath.org/index.php/scenarios" role="button">Learn more
@ -185,7 +205,7 @@
{% endfor %}
{% else %}
{% for obj in reviewed_objects|slice:":50" %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}{# <i>({{ obj.package.name }})</i> #}
<span class="glyphicon glyphicon-star" aria-hidden="true"
style="float:right" data-toggle="tooltip"
data-placement="top" title="" data-original-title="Reviewed">
@ -231,6 +251,7 @@
</div>
<script>
$(function () {
$('#modal-form-delete-submit').on('click', function (e) {
e.preventDefault();
$('#modal-form-delete').submit();

View File

@ -0,0 +1,14 @@
{% extends "framework.html" %}
{% load static %}
{% block content %}
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">Something went wrong!</h4>
<p>An unexpected Error occurred...</p>
<hr>
<p class="mb-0">
The error was logged and will be investigated.
</p>
</div>
{% endblock content %}

View File

@ -2,9 +2,9 @@
{% load static %}
{% block content %}
<div class="alert alert-error" role="alert">
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">Your account has not been activated yet!</h4>
<p>Your account has not been activated yet. If you have question <a href="mailto:admin@envipath.org">contact
<p>Your account has not been activated yet. If you have questions <a href="mailto:admin@envipath.org">contact
us.</a>
</p>
</div>

View File

@ -14,7 +14,19 @@
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/css/bootstrap-select.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/js/bootstrap-select.min.js"></script>
<!-- CDN END -->
<meta name="csrf-token" content="{{ csrf_token }}">
<script>
const csrftoken = document.querySelector('[name=csrf-token]').content;
// Setup CSRF header for all jQuery AJAX requests
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
</script>
{# Favicon #}
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
<!-- {# C3 CSS #}-->
@ -40,7 +52,9 @@
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '7']);
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
g.async = true;
g.src = u + 'matomo.js';
s.parentNode.insertBefore(g, s);
})();
</script>
<!-- End Matomo Code -->
@ -95,16 +109,16 @@
<li><a href="{{ meta.server_url }}/reaction" id="reactionLink">Reaction</a></li>
<li><a href="{{ meta.server_url }}/model" id="relative-reasoningLink">Model</a></li>
<li><a href="{{ meta.server_url }}/scenario" id="scenarioLink">Scenario</a></li>
<li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</a></li>
<!-- <li><a href="{{ meta.server_url }}/user" id="userLink">User</a></li>-->
<!-- <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>-->
{# <li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</a></li>#}
{# <li><a href="{{ meta.server_url }}/user" id="userLink">User</a></li>#}
{# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#}
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right navbar-nav-framework navbar-right-framework">
<!-- <li><a href="{{ meta.server_url }}/search" id="searchLink">Search</a></li>-->
{# <li><a href="{{ meta.server_url }}/search" id="searchLink">Search</a></li>#}
<li class="dropdown">
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a>
<ul role="menu" class="dropdown-menu">
@ -126,7 +140,8 @@
</li>
{% else %}
<li class="dropdown">
<a data-toggle="dropdown" id="loggedInButton" class="dropdown-toggle" id="logedInButton" href="#">
<a data-toggle="dropdown" id="loggedInButton" class="dropdown-toggle" id="logedInButton"
href="#">
<div id="username">
{{ user.username }}<b class="caret"></b>
</div>
@ -136,7 +151,8 @@
<a href="{{ meta.user.url }}" id="accountbutton">My Account</a>
</li>
<li class="divider"></li>
<form class="navbar-form navbar-left navbar-left-framework" role="logout" action="{{ meta.user.url }}" method="post">
<form class="navbar-form navbar-left navbar-left-framework" role="logout"
action="{{ meta.user.url }}" method="post">
{% csrf_token %}
<div class="form-group">
<input type="hidden" name="logout" value="true">
@ -213,7 +229,14 @@
</ul>
</div>
</div>
<script>
$(function () {
// Hide actionsbutton if theres no action defined
if ($('#actionsButton ul').children().length > 0) {
$('#actionsButton').show();
}
});
</script>
{% block modals %}
{% include "modals/cite_modal.html" %}
{% include "modals/signup_modal.html" %}

View File

@ -3,7 +3,8 @@
{% block content %}
<!-- TODO rename ids as well as remove pathways if modal is closed!-->
<div class="modal fade" tabindex="-1" id="foundMatching" role="dialog" aria-labelledby="foundModal" aria-hidden="true">
<div class="modal fade" tabindex="-1" id="foundMatching" role="dialog" aria-labelledby="foundModal"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
@ -24,7 +25,8 @@
</div>
<div class="modal-footer">
<a id="modal-predict" class="btn btn-primary" href="#">Predict</a>
<button type="button" id="cancel-predict" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" id="cancel-predict" class="btn btn-default" data-dismiss="modal">Cancel
</button>
</div>
</div>
</div>
@ -47,10 +49,12 @@
</p>
</div>
<p></p>
<form id="index-form" action="{{ meta.current_package.url }}/pathway" method="POST">
{% csrf_token %}
<div class="input-group" id="index-form-bar">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
aria-expanded="false">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
@ -60,20 +64,26 @@
</li>
</ul>
</div>
<input type="text" class="form-control" id='index-form-text-input' placeholder="Enter a SMILES to predict a Pathway or type something to search">
<input type="text" class="form-control" id='index-form-text-input'
placeholder="Enter a SMILES to predict a Pathway or type something to search">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true"
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false" id="action-button">Predict <span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a id="dropdown-predict">Predict</a></li>
<li><a id="dropdown-search">Search</a></li>
</ul>
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="run-button">Go!</button>
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="run-button">Go!
</button>
</div>
</div>
<input type="hidden" id="index-form-smiles" name="smiles" value="smiles">
<input type="hidden" id="index-form-predict" name="predict" value="predict">
</form>
<div id="loading"></div>
<script language="javascript">
var currentPackage = "{{ meta.current_package.url }}";
function goButtonClicked() {
$(this).prop("disabled", true);
@ -81,23 +91,40 @@
var action = $('#action-button').text().trim();
var textSmiles = $('#index-form-text-input').val().trim();
if (textSmiles === '') {
return;
}
var ketcherSmiles = getKetcher('index-form-ketcher').getSmiles().trim();
if (action != 'Search' && ketcherSmiles != '' && textSmiles != ketcherSmiles) {
if (action !== 'Search' && ketcherSmiles !== '' && textSmiles !== ketcherSmiles) {
console.log("Ketcher and TextInput differ!");
}
if (action == 'Search') {
console.log("Searching...");
if (action === 'Search') {
var par = {};
par['search'] = textSmiles;
par['mode'] = 'text';
var queryString = $.param(par, true);
window.location.href = "/search?" + queryString;
} else {
console.log("Predicting");
$('#index-form-smiles').val(textSmiles);
$('#index-form').submit();
}
}
function actionDropdownClicked() {
var suffix = ' <span class="caret"></span>';
var dropdownVal = $(this).text();
if (dropdownVal === 'Search') {
$("#index-form").attr("action", '/search');
$("#index-form").attr("method", 'GET');
} else {
$("#index-form").attr("action", currentPackage + "/pathway");
}
$('#action-button').html(dropdownVal + suffix);
}

189
templates/login.html Normal file
View File

@ -0,0 +1,189 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login Modal with Blur</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap 3.3.7 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<style>
body, html {
margin: 0;
height: 100%;
overflow: hidden;
}
.bg-blur {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('{% static "/images/enviPy-screenshot.png" %}') no-repeat center center/cover;
filter: blur(8px);
z-index: -1;
}
/* Optional: dim layer */
.bg-dim {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
z-index: 0;
}
.center-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
}
</style>
</head>
<body>
<!-- Blurred Background -->
<div class="bg-blur"></div>
<div class="bg-dim"></div>
<!-- Trigger Button -->
<div class="center-button">
<button class="btn btn-primary btn-lg" data-toggle="modal" data-target="#signupmodal">Login / Sign Up</button>
</div>
<!-- Bootstrap Modal -->
<div class="modal fade bs-modal-sm" id="signupmodal" tabindex="-1" role="dialog"
aria-labelledby="mySmallModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<br>
<div class="bs-example bs-example-tabs">
<ul id="myTab" class="nav nav-tabs">
<li class="active">
<a href="#signin" data-toggle="tab">Sign In</a>
</li>
<li class="">
<a href="#signup" data-toggle="tab">Register</a>
</li>
<li class="">
<a href="#why" data-toggle="tab">Why?</a>
</li>
</ul>
</div>
<div class="modal-body">
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="signin">
<form class="form-horizontal" method="post" action="{{ meta.server_url }}/user">
{% csrf_token %}
<fieldset>
<input type="hidden" name="login" id="login" value="true"/>
<div class="control-group">
<label class="control-label" for="username">Username:</label>
<div class="controls">
<input required id="username" name="username" type="text"
class="form-control"
placeholder="username" autocomplete="username">
</div>
<label class="control-label" for="passwordinput">Password:</label>
<div class="controls">
<input required id="passwordinput" name="password" class="form-control"
type="password" placeholder="********"
autocomplete="current-password">
</div>
</div>
<!-- Button -->
<div class="control-group">
<label class="control-label" for="signin"></label>
<div class="controls">
<button id="signin" name="signin" class="btn btn-success">Sign In
</button>
</div>
</div>
</fieldset>
</form>
</div>
<!-- Why tab -->
<div class="tab-pane fade in" id="why">
<p>After you register, you have more permissions on
this site, e.g., can create your own
packages, submit data for review, and set access
permissions to your data.</p>
<p></p>
<p>
<br> Please
contact <a href="mailto:admin@envipath.org">admin@envipath.org</a>
if you have any questions.</p>
</div>
<!-- Register -->
<div class="tab-pane fade"
id="signup">
<div id="passwordGuideline" class="alert alert-info">
The password must contain 8 to 30 characters<br>
The following characters are allowed:
- Upper and lower case characters<br>
- Digits<br>
- Special characters _, -, +<br>
</div>
<form id="signup-action" class="form-horizontal" action="{{ meta.server_url }}/user"
method="post">
{% csrf_token %}
<input type="hidden" name="register" id="register" value="true"/>
<label class="control-label" for="userid">Username:</label>
<input id="userid" name="username" class="form-control" type="text"
placeholder="user" required autocomplete="username">
<label class="control-label" for="email">Email:</label>
<input id="email" name="email" class="form-control" type="email"
placeholder="user@envipath.org" required>
<label class="control-label" for="password">Password:</label>
<input id="password" name="password" class="form-control" type="password"
placeholder="********" required autocomplete="new-password">
<label class="control-label" for="rpassword">Repeat Password:</label>
<input id="rpassword" name="rpassword" class="form-control" type="password"
placeholder="********" required autocomplete="new-password">
<div class="control-group">
<label class="control-label" for="confirmsignup"></label>
<div class="controls">
<button id="confirmsignup" name="confirmsignup" class="btn btn-success">Sign
Up
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="modal-footer">
<center>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</center>
</div>
</div>
</div>
</div>
<!-- Bootstrap 3.3.7 JS + jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</body>
</html>

View File

@ -16,23 +16,11 @@
<input id="rule-name" class="form-control" name="rule-name" placeholder="Name"/>
<label for="rule-description">Description</label>
<input id="rule-description" class="form-control" name="rule-description" placeholder="Description"/>
<label for="rule-smirks">SMIRKS</label>
<input id="rule-smirks" class="form-control" name="rule-smirks" placeholder="SMIRKS"/>
<p></p>
<!-- TODO Ruletypes -->
<!-- TODO Decide on rules to use?-->
<div id="rule-smirks-viz"></div>
<input type="hidden" name="rule-type" id="rule-type" value="SimpleAmbitRule">
<!-- <select id="rule-type" name="rule-type" class="form-control" data-width='100%'>-->
<!-- <option disabled selected>Select Rule Type</option>-->
<!-- {% for k, v in rule_types.items %}-->
<!-- <option value="{{ v }}">{{ k }}</option>-->
<!-- {% endfor %}-->
<!-- </select>-->
<!-- -->
<div>
<iframe id="new_rule_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
height="510"></iframe>
</div>
<input type="hidden" name="rule-smirks" id="rule-smirks">
<p></p>
</form>
</div>
@ -47,13 +35,36 @@
</div>
<script>
$(function() {
$('#rule-smirks').on('input', function(e) {
$('#rule-smirks-viz').empty()
smirks = $('#rule-smirks').val()
const img = new Image();
img.src = "{% url 'depict' %}?is_query_smirks=true&smirks=" + encodeURIComponent(smirks);
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'cover';
img.onload = function () {
$('#rule-smirks-viz').append(img);
};
img.onerror = function () {
error_tpl = `
<div class="alert alert-error" role="alert">
<h4 class="alert-heading">Could not render SMIRKS!</h4>
<p>Could not render SMIRKS - Have you entered a valid SMIRKS?</a>
</p>
</div>`
$('#rule-smirks-viz').append(error_tpl);
};
});
$('#new_rule_modal_form_submit').on('click', function(e) {
e.preventDefault();
$(this).prop("disabled",true);
k = getKetcher('new_rule_ketcher');
$('#rule-smirks').val(k.getSmiles());
// submit form
$('#new_rule_modal_form').submit();
});

View File

@ -0,0 +1,126 @@
{% load static %}
<div class="modal fade bs-modal-lg" id="add_pathway_edge_modal" tabindex="-1" aria-labelledby="add_pathway_edge_modal"
aria-modal="true"
role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Add a Reaction</h4>
</div>
<div class="modal-body">
<form id="add_pathway_edge_modal_form" accept-charset="UTF-8"
action="{% url 'package pathway edge list' meta.current_package.uuid pathway.uuid %}"
data-remote="true" method="post">
{% csrf_token %}
<label for="edge-name">Name</label>
<input id="edge-name" class="form-control" name="edge-name" placeholder="Name"/>
<label for="edge-description">Description</label>
<input id="edge-description" class="form-control" name="edge-description" placeholder="Description"/>
<p></p>
<div class="row">
<div class="col-xs-5">
<legend>Substrate(s)</legend>
</div>
<div class="col-xs-2">
</div>
<div class="col-xs-5">
<legend>Product(s)</legend>
</div>
</div>
<div class="row">
<div class="col-xs-5">
<select id="add_pathway_edge_substrates" name="edge-substrates"
data-actions-box='true' class="form-control" multiple data-width='100%'>
{% for n in pathway.nodes %}
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-xs-2" style="display: flex; justify-content: center; align-items: center;">
<i class="glyphicon glyphicon-arrow-right"></i>
</div>
<div class="col-xs-5">
<select id="add_pathway_edge_products" name="edge-products"
data-actions-box='true' class="form-control" multiple data-width='100%'>
{% for n in pathway.nodes %}
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<p></p>
<div class="col-xs-12" id="reaction_image">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
</button>
<button type="button" class="btn btn-primary" id="add_pathway_edge_modal_form_submit">Submit</button>
</div>
</div>
</div>
</div>
<script>
function reactionImage() {
var substrates = [];
$('#add_pathway_edge_substrates option:selected').each(function () {
var smiles = $(this).data('smiles'); // read data-smiles attribute
substrates.push(smiles);
});
var products = []
$('#add_pathway_edge_products option:selected').each(function () {
var smiles = $(this).data('smiles'); // read data-smiles attribute
products.push(smiles);
});
if (substrates.length > 0 && products.length > 0) {
reaction = substrates.join('.') + ">>" + products.join('.');
$('#reaction_image').empty();
$('#reaction_image').append(
"<img width='100%' src='{% url 'depict' %}?smirks=" + encodeURIComponent(reaction) +"'>"
);
}
}
$(function () {
$("#add_pathway_edge_substrates").selectpicker();
$("#add_pathway_edge_products").selectpicker();
$("#add_pathway_edge_substrates").on('change', function (e) {
reactionImage();
})
$("#add_pathway_edge_products").on('change', function (e) {
reactionImage();
})
$(function () {
$('#add_pathway_edge_modal_form_submit').on('click', function (e) {
e.preventDefault();
$(this).prop("disabled", true);
// submit form
$('#add_pathway_edge_modal_form').submit();
});
});
});
</script>

View File

@ -0,0 +1,78 @@
{% load static %}
<div class="modal fade bs-modal-lg" id="add_pathway_node_modal" tabindex="-1" aria-labelledby="add_pathway_node_modal" aria-modal="true"
role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Add a Node</h4>
</div>
<div class="modal-body">
<form id="add_pathway_node_modal_form" accept-charset="UTF-8" action="{% url 'package pathway node list' meta.current_package.uuid pathway.uuid %}" data-remote="true" method="post">
{% csrf_token %}
<label for="node-name">Name</label>
<input id="node-name" class="form-control" name="node-name" placeholder="Name"/>
<label for="node-description">Description</label>
<input id="node-description" class="form-control" name="node-description" placeholder="Description"/>
<label for="node-smiles">SMILES</label>
<input type="text" class="form-control" name="node-smiles" placeholder="SMILES" id="node-smiles">
<p></p>
<div>
<iframe id="add_node_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
height="510"></iframe>
</div>
<p></p>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
</button>
<button type="button" class="btn btn-primary" id="add_pathway_node_modal_form_submit">Submit</button>
</div>
</div>
</div>
</div>
<script>
function newStructureModalketcherToNewStructureModalTextInput() {
$('#node-smiles').val(this.ketcher.getSmiles());
}
$(function() {
$('#add_node_ketcher').on('load', function() {
const checkKetcherReady = () => {
win = this.contentWindow
if (win.ketcher && 'editor' in win.ketcher) {
win.ketcher.editor.event.change.handlers.push({
once: false,
priority: 0,
f: newStructureModalketcherToNewStructureModalTextInput,
ketcher: win.ketcher
});
} else {
setTimeout(checkKetcherReady, 100);
}
};
checkKetcherReady();
})
$(function() {
$('#add_pathway_node_modal_form_submit').on('click', function(e) {
e.preventDefault();
$(this).prop("disabled",true);
// submit form
$('#add_pathway_node_modal_form').submit();
});
});
});
</script>

View File

@ -0,0 +1,35 @@
{% load static %}
<!-- Delete Model -->
<div id="delete_model_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Model</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deletes the Model.
<form id="delete-model-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete-model"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-model-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$('#delete-model-modal-submit').click(function (e) {
e.preventDefault();
$('#delete-model-modal-form').submit();
});
})
</script>

View File

@ -0,0 +1,35 @@
{% load static %}
<!-- Delete Node -->
<div id="delete_node_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Node</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deletes the Node as well as ingoing and outgoing edges.
<form id="delete-node-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete-node"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-node-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$('#delete-node-modal-submit').click(function (e) {
e.preventDefault();
$('#delete-node-modal-form').submit();
});
})
</script>

View File

@ -0,0 +1,65 @@
{% load static %}
<!-- Delete Edge -->
<div id="delete_pathway_edge_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Edge</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deletes the Edge. Nodes referenced by this edge will remain.
<p></p>
<form id="delete-pathway-edge-modal-form" accept-charset="UTF-8" action="" data-remote="true"
method="post">
{% csrf_token %}
<select id="delete_pathway_edge_edges" name="edge-url"
data-actions-box='true' class="form-control" data-width='100%'>
<option value="" disabled selected>Select Reaction to delete</option>
{% for e in pathway.edges %}
<option value="{{ e.url }}">{{ e.edge_label.name }}</option>
{% endfor %}
</select>
<input type="hidden" id="hidden" name="hidden" value="delete-edge"/>
</form>
<p></p>
<div id="delete_pathway_edge_image"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-pathway-edge-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$("#delete_pathway_edge_edges").selectpicker();
$("#delete_pathway_edge_edges").on('change', function (e) {
edge_url = $('#delete_pathway_edge_edges option:selected').val()
if (edge_url !== "") {
$('#delete_pathway_edge_image').empty();
$('#delete_pathway_edge_image').append(
"<img width='100%' src='" + edge_url + "?image=svg'>"
);
}
})
$('#delete-pathway-edge-modal-submit').click(function (e) {
e.preventDefault();
edge_url = $('#delete_pathway_edge_edges option:selected').val()
if (edge_url === "") {
return;
}
$('#delete-pathway-edge-modal-form').attr('action', edge_url)
$('#delete-pathway-edge-modal-form').submit();
});
})
</script>

View File

@ -0,0 +1,35 @@
{% load static %}
<!-- Delete Pathway -->
<div id="delete_pathway_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Pathway</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deletes the Pathway together with all Nodes and Edges.
<form id="delete-pathway-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete-pathway"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-pathway-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$('#delete-pathway-modal-submit').click(function (e) {
e.preventDefault();
$('#delete-pathway-modal-form').submit();
});
})
</script>

View File

@ -0,0 +1,65 @@
{% load static %}
<!-- Delete Node -->
<div id="delete_pathway_node_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Node</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deletes the Node. Edges having this Node as Substrate or Product will be removed as well.
<p></p>
<form id="delete-pathway-node-modal-form" accept-charset="UTF-8" action="" data-remote="true"
method="post">
{% csrf_token %}
<select id="delete_pathway_node_nodes" name="node-url"
data-actions-box='true' class="form-control" data-width='100%'>
<option value="" disabled selected>Select Compound to delete</option>
{% for n in pathway.nodes %}
<option value="{{ n.url }}">{{ n.default_node_label.name }}</option>
{% endfor %}
</select>
<input type="hidden" id="hidden" name="hidden" value="delete-node"/>
</form>
<p></p>
<div id="delete_pathway_node_image"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-pathway-node-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$("#delete_pathway_node_nodes").selectpicker();
$("#delete_pathway_node_nodes").on('change', function (e) {
node_url = $('#delete_pathway_node_nodes option:selected').val()
if (node_url !== "") {
$('#delete_pathway_node_image').empty();
$('#delete_pathway_node_image').append(
"<img width='100%' src='" + node_url + "?image=svg'>"
);
}
})
$('#delete-pathway-node-modal-submit').click(function (e) {
e.preventDefault();
node_url = $('#delete_pathway_node_nodes option:selected').val()
if (node_url === "") {
return;
}
$('#delete-pathway-node-modal-form').attr('action', node_url)
$('#delete-pathway-node-modal-form').submit();
});
})
</script>

View File

@ -0,0 +1,35 @@
{% load static %}
<!-- Delete Reaction -->
<div id="delete_reaction_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Reaction</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deletes the Reaction.
<form id="delete-reaction-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete-reaction"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-reaction-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$('#delete-reaction-modal-submit').click(function (e) {
e.preventDefault();
$('#delete-reaction-modal-form').submit();
});
})
</script>

View File

@ -4,13 +4,13 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create a Compound</h5>
<h5 class="modal-title">Edit Compound</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Edit a Compound.</p>
<p>Edit Compound.</p>
<form id="edit-compound-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<p>

View File

@ -0,0 +1,46 @@
{% load static %}
<!-- Edit Node -->
<div id="edit_node_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit Node</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Edit Node.</p>
<form id="edit-node-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<p>
<label for="node-name">Name</label>
<input id="node-name" class="form-control" name="node-name" value="{{ node.name}}">
</p>
<p>
<label for="node-description">Description</label>
<input id="node-description" type="text" class="form-control"
value="{{ node.description }}"
name="node-description">
</p>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="edit-node-modal-submit">Create</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
$('#edit-node-modal-submit').click(function(e){
e.preventDefault();
$('#edit-node-modal-form').submit();
});
});
</script>

View File

@ -0,0 +1,43 @@
{% load static %}
<!-- Edit Pathway -->
<div id="edit_pathway_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit Pathway</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Edit Pathway.</p>
<form id="edit-pathway-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<p>
<label for="pathway-name">Name</label>
<input id="pathway-name" class="form-control" name="pathway-name" value="{{ pathway.name }}">
</p>
<p>
<label for="pathway-description">Description</label>
<textarea id="pathway-description" type="text" class="form-control" name="pathway-description"
rows="10">{{ pathway.description }}</textarea>
</p>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="edit-pathway-modal-submit">Update</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
$('#edit-pathway-modal-submit').click(function(e){
e.preventDefault();
$('#edit-pathway-modal-form').submit();
});
})
</script>

View File

@ -10,7 +10,6 @@
<h3 class="modal-title">Update Reaction</h3>
</div>
<div class="modal-body">
<!-- <p>Edit a Reaction.</p>-->
<form id="edit-reaction-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<p>

View File

@ -0,0 +1,44 @@
{% load static %}
<!-- Edit Rule -->
<div id="edit_rule_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h3 class="modal-title">Update Rule</h3>
</div>
<div class="modal-body">
<form id="edit-rule-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<p>
<label for="rule-name">Name</label>
<input id="rule-name" class="form-control" name="rule-name" value="{{ rule.name }}">
</p>
<p>
<label for="rule-description">Description</label>
<input id="rule-description" type="text" class="form-control"
value="{{ rule.description }}" name="rule-description">
</p>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="edit-rule-modal-submit">Update</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
$('#edit-rule-modal-submit').click(function(e){
e.preventDefault();
$('#edit-rule-modal-form').submit();
});
});
</script>

View File

@ -70,7 +70,6 @@ $(function() {
e.preventDefault();
const formData = $('#request_api_token_form').serialize();
console.log(formData)
$.post('', formData, function(response) {
$('#new-token-pre').text(response.raw_token);
$('#new-token').show();

View File

@ -11,7 +11,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ rule.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -13,7 +13,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ compound.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
@ -27,7 +27,10 @@
</div>
<div class="panel-body">
<p>
The structures stored in this compound can be found at <a target="_blank" href="{{compound.url}}/structure" role="button">Compound structures &gt;&gt;</a>
The structures stored in this compound can be found at <a target="_blank"
href="{{ compound.url }}/structure"
role="button">Compound structures
&gt;&gt;</a>
</p>
</div>
@ -94,11 +97,12 @@
</div>
<div id="compound-inchi" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ compound.default_structure.InChIKey }}
{{ compound.default_structure.inchikey }}
</div>
</div>
<!-- Reactions -->
{% if compound.related_reactions %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound-reaction-link" data-toggle="collapse" data-parent="#compound-detail"
@ -112,8 +116,10 @@
{% endfor %}
</div>
</div>
{% endif %}
<!-- Pathways -->
{% if compound.related_pathways %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound-pathway-link" data-toggle="collapse" data-parent="#compound-detail"
@ -127,7 +133,7 @@
{% endfor %}
</div>
</div>
{% endif %}
<!-- External Identifiers -->
</div>

View File

@ -11,7 +11,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ compound_structure.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

View File

@ -13,7 +13,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ group.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -1,9 +1,10 @@
{% extends "framework.html" %}
{% load static %}
{% load envipytags %}
{% block content %}
{% block action_modals %}
{% include "modals/objects/delete_model_modal.html" %}
{% endblock action_modals %}
<!-- Include required libs -->
@ -16,13 +17,14 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ model.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu">
{% block actions %}
{% include "actions/objects/model.html" %}
{% endblock %}
</ul>
</div>
@ -30,6 +32,64 @@
<div class="panel-body">
<p> {{ model.description }} </p>
</div>
{% if model|classname == 'MLRelativeReasoning' %}
<!-- Rule Packages -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-package-link" data-toggle="collapse" data-parent="#model-detail"
href="#rule-package">Rule Packages</a>
</h4>
</div>
<div id="rule-package" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for p in model.rule_packages.all %}
<a class="list-group-item" href="{{ p.url }}">{{ p.name }}</a>
{% endfor %}
</div>
</div>
<!-- Reaction Packages -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="reaction-package-link" data-toggle="collapse" data-parent="#model-detail"
href="#reaction-package">Rule Packages</a>
</h4>
</div>
<div id="reaction-package" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for p in model.data_packages.all %}
<a class="list-group-item" href="{{ p.url }}">{{ p.name }}</a>
{% endfor %}
</div>
</div>
{% if model.eval_packages.all|length > 0 %}
<!-- Eval Packages -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="eval-package-link" data-toggle="collapse" data-parent="#model-detail"
href="#eval-package">Rule Packages</a>
</h4>
</div>
<div id="eval-package" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for p in model.eval_packages.all %}
<a class="list-group-item" href="{{ p.url }}">{{ p.name }}</a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Model Status -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="model-status-link" data-toggle="collapse" data-parent="#model-detail"
href="#model-status">Model Status</a>
</h4>
</div>
<div id="model-status" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ model.status }}
</div>
</div>
{% endif %}
<!-- Predict Panel -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
@ -40,7 +100,8 @@
<div id="predict-smiles" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<div class="input-group">
<input id="smiles-to-predict" type="text" class="form-control" placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C">
<input id="smiles-to-predict" type="text" class="form-control"
placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C">
<span class="input-group-btn">
<button class="btn btn-default" type="submit" id="predict-button">Predict!</button>
</span>
@ -55,7 +116,7 @@
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="sg-curve-link" data-toggle="collapse" data-parent="#model-detail"
href="#sg-curve">Predict</a>
href="#sg-curve">Precision Recall Curve</a>
</h4>
</div>
<div id="sg-curve" class="panel-collapse collapse in">
@ -74,8 +135,6 @@
if (!($('#sg-chart').length > 0)) {
return;
}
console.log($('#sg-chart').length)
//$('#sg-curve-plotdiv').empty();
var x = ['Recall'];
var y = ['Precision'];

View File

@ -0,0 +1,75 @@
{% extends "framework.html" %}
{% block content %}
{% block action_modals %}
{% include "modals/objects/edit_node_modal.html" %}
{% include "modals/objects/delete_node_modal.html" %}
{% endblock action_modals %}
<div class="panel-group" id="node-detail">
<div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ node.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu">
{% block actions %}
{% include "actions/objects/node.html" %}
{% endblock %}
</ul>
</div>
</div>
<div class="panel-body">
The underlying structure can be found <a href="{{ node.default_node_label.url }}">here</a>.
</div>
<!-- Description -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="node-desc-link" data-toggle="collapse" data-parent="#node-detail"
href="#node-desc">Description</a>
</h4>
</div>
<div id="node-desc" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ node.description }}
</div>
</div>
<!-- Image -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="node-image-link" data-toggle="collapse" data-parent="#node-detail"
href="#node-image">Image Representation</a>
</h4>
</div>
<div id="node-image" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<div id="image-div" align="center">
{{ node.default_node_label.as_svg|safe }}
</div>
</div>
</div>
<!-- SMILES -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="node-smiles-link" data-toggle="collapse" data-parent="#node-detail"
href="#node-smiles">SMILES Representation</a>
</h4>
</div>
<div id="node-smiles" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ node.default_node_label.smiles }}
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -14,7 +14,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ package.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -4,18 +4,60 @@
{% load static %}
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
svg { width: 100%; height: 600px; color: red;}
.link { stroke: #999; stroke-opacity: 0.6; marker-end: url(#arrow); }
.link_no_arrow { stroke: #999; stroke-opacity: 0.6; }
.node image { cursor: pointer; }
.node circle { fill: lightblue; stroke: steelblue; stroke-width: 1.5px; }
.highlighted { stroke: red; stroke-width: 3px; }
.tooltip { position: absolute; background: lightgrey; padding: 5px; border-radius: 5px; visibility: hidden; opacity: 1}
svg {
width: 100%;
height: 600px;
color: red;
}
.link {
stroke: #999;
stroke-opacity: 0.6;
marker-end: url(#arrow);
}
.link_no_arrow {
stroke: #999;
stroke-opacity: 0.6;
}
.node image {
cursor: pointer;
}
.node circle {
fill: lightblue;
stroke: steelblue;
stroke-width: 1.5px;
}
.highlighted {
stroke: red;
stroke-width: 3px;
}
.tooltip {
position: absolute;
background: lightgrey;
padding: 5px;
border-radius: 5px;
visibility: hidden;
opacity: 1
}
</style>
<script src="{% static 'js/pw.js' %}"></script>
<p></p>
{% block action_modals %}
{% include "modals/objects/add_pathway_node_modal.html" %}
{% include "modals/objects/add_pathway_edge_modal.html" %}
{% include "modals/objects/edit_pathway_modal.html" %}
{% include "modals/objects/delete_pathway_node_modal.html" %}
{% include "modals/objects/delete_pathway_edge_modal.html" %}
{% include "modals/objects/delete_pathway_modal.html" %}
{% endblock action_modals %}
<p></p>
<div id="pwcontent">
<div class="panel-group" id="pwAccordion">
<div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
@ -36,16 +78,16 @@
<div id="editbarCollapse" class="collapse navbar-collapse ">
<ul class="nav navbar-nav">
<li class="dropdown requiresWritePerm">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true"
aria-expanded="false">
<span class="glyphicon glyphicon-edit"></span>
Edit
<span class="caret"></span></a>
<ul id="editingList" class="dropdown-menu">
<li>
<a role="button" data-toggle="modal" id="showCompoundNames">
<span class="glyphicon glyphicon-eye-open"></span> Show Compound Names</a>
</li>
{% block actions %}
{% include "actions/objects/pathway.html" %}
{% endblock %}
</ul>
</li>
</ul>
@ -53,13 +95,30 @@
<ul class="nav navbar-nav navbar-right">
<li>
<a role="button" data-toggle="modal" onclick="goFullscreen('pwcontent')">
<span class="glyphicon glyphicon-fullscreen"></span>Fullscreen
<span class="glyphicon glyphicon-fullscreen"></span>
Fullscreen
</a>
</li>
<li>
<button type="button" class="btn btn-default navbar-btn" data-toggle="tooltip" id="status"
data-original-title="" title="" data-content="Pathway prediction complete."><span
class="glyphicon glyphicon-ok"></span></button>
{% if pathway.completed %}
<button type="button" class="btn btn-default navbar-btn" data-toggle="tooltip"
id="status" data-original-title="" title=""
data-content="Pathway prediction complete.">
<span class="glyphicon glyphicon-ok"></span>
</button>
{% elif pathway.failed %}
<button type="button" class="btn btn-default navbar-btn" data-toggle="tooltip"
id="status" data-original-title="" title=""
data-content="Pathway prediction failed.">
<span class="glyphicon glyphicon-remove"></span>
</button>
{% else %}
<button type="button" class="btn btn-default navbar-btn" data-toggle="tooltip"
id="status" data-original-title="" title=""
data-content="Pathway prediction running.">
<img height="20" src="{% static '/images/wait.gif' %}">
</button>
{% endif %}
&nbsp;
</li>
</ul>
@ -94,8 +153,18 @@
</div>
</div>
</div>
</div>
<script>
function goFullscreen(id) {
var element = document.getElementById(id);
if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
}
}
function transformReferences(text) {
return text.replace(/\[\s*(http[^\]|]+)\s*\|\s*([^\]]+)\s*\]/g, '<a target="parent" href="$1">$2</a>');
}

View File

@ -4,6 +4,7 @@
{% block action_modals %}
{% include "modals/objects/edit_reaction_modal.html" %}
{% include "modals/objects/delete_reaction_modal.html" %}
{% endblock action_modals %}
<div class="panel-group" id="reaction-detail">
@ -11,7 +12,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ reaction.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
@ -64,7 +65,8 @@
{% for educt in reaction.educts.all %}
<a class="btn btn-default" href="{{ educt.url }}">{{ educt.name }}</a>
{% endfor %}
<span class="glyphicon glyphicon-arrow-right" style="margin-left:5em;margin-right:5em;" aria-hidden="true"></span>
<span class="glyphicon glyphicon-arrow-right" style="margin-left:5em;margin-right:5em;"
aria-hidden="true"></span>
{% for product in reaction.products.all %}
<a class="btn btn-default" href="{{ product.url }}">{{ product.name }}</a>
{% endfor %}
@ -84,6 +86,24 @@
</div>
</div>
{% if reaction.rules.all %}
<!-- Rules -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="reaction-rules-link" data-toggle="collapse" data-parent="#reaction-detail"
href="#reaction-rules">Rules</a>
</h4>
</div>
<div id="reaction-rules" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for r in reaction.rules.all %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
{% endfor %}
</div>
</div>
{% endif %}
{% if reaction.related_pathways %}
<!-- Pathways -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
@ -100,6 +120,7 @@
</div>
</div>
{% endif %}
</div>
{% endblock content %}

View File

@ -10,7 +10,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ scenario.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -11,7 +11,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ rule.name }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
@ -71,6 +71,7 @@
</div>
<!-- Reactant Filter SMARTS -->
{% if rule.reactant_filter_smarts %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-reactant-filter-smarts-link" data-toggle="collapse" data-parent="#rule-detail"
@ -82,6 +83,7 @@
<p> {{ rule.reactant_filter_smarts }} </p>
</div>
</div>
{% endif %}
<!-- Products SMARTS -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
@ -97,6 +99,7 @@
</div>
<!-- Product Filter SMARTS -->
{% if rule.product_filter_smarts %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-product-filter-smarts-link" data-toggle="collapse" data-parent="#rule-detail"
@ -108,8 +111,10 @@
<p> {{ rule.product_filter_smarts }} </p>
</div>
</div>
{% endif %}
<!-- Included in Composite Rules -->
{% if rule.parallelrule_set.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-composite-rule-link" data-toggle="collapse" data-parent="#rule-detail"
@ -123,8 +128,10 @@
{% endfor %}
</div>
</div>
{% endif %}
<!-- Scenarios -->
{% if rule.scenarios.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-scenario-link" data-toggle="collapse" data-parent="#rule-detail"
@ -138,8 +145,10 @@
{% endfor %}
</div>
</div>
{% endif %}
<!-- Reactions -->
{% if rule.related_reactions %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-reaction-link" data-toggle="collapse" data-parent="#rule-detail"
@ -153,8 +162,10 @@
{% endfor %}
</div>
</div>
{% endif %}
<!-- Pathways -->
{% if rule.related_pathways %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-pathway-link" data-toggle="collapse" data-parent="#rule-detail"
@ -168,7 +179,7 @@
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@ -15,7 +15,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ user.username }}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -1,12 +1,6 @@
{% extends "framework.html" %}
{% load static %}
{% block content %}
<!--<script>-->
<!--$(document).arrive(".selPackages", function() {-->
<!-- // selectpicker triggers 'bootstrap-select' library-->
<!-- $(this).selectpicker();-->
<!--});-->
<!--</script>-->
<div id=searchContent>
<div id="packSelector">
@ -31,9 +25,10 @@
<div>
<label>Search Term</label><br>
<div class="input-group" id="index-form-bar">
<input type="text" class="form-control" id='index-form-text-input' placeholder="Benfuracarb">
<input type="text" class="form-control" id='searchbar' placeholder="Benfuracarb">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" id="action-button"
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
id="mode-button"
aria-haspopup="true" aria-expanded="false">Text <span class="caret"></span></button>
<ul class="dropdown-menu">
<li class="dropdown-header">Text</li>
@ -45,16 +40,119 @@
<li class="dropdown-header">InChI</li>
<li><a id="dropdown-search-inchi-inchikey">InChIKey</a></li>
</ul>
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="run-button">Go!
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="search-button">
Go!
</button>
</div>
</div>
<div id="results">
</div>
<p></p>
<div id="results"></div>
<p></p>
<div id="loading"></div>
</div>
</div>
<script>
function modeDropdownClicked() {
var suffix = ' <span class="caret"></span>';
var dropdownVal = $(this).text();
$('#mode-button').html(dropdownVal + suffix);
}
function handleSearchResponse(id, data) {
content = `
<div class='panel-group' id='search-accordion'>
<div class='panel panel-default'>
<div class='panel-heading' id='headingPanel' style='font-size:2rem;height: 46px'>
Results
</div>
<div id='descDiv'></div>
</div>`;
function makeContent(objs) {
links = "";
for (idx in objs) {
obj = objs[idx];
links += `<a class='list-group-item' href='${obj.url}'>${obj.name}</a>`
}
return links;
}
allEmpty = true;
for (key in data) {
if (data[key].length < 1) {
continue;
}
allEmpty = false;
content += `
<div class='panel panel-default panel-heading list-group-item' style='background-color:silver'>
<h4 class='panel-title'>
<a id='${key}_link' data-toggle='collapse' data-parent='#search-accordion' href='#${key}_panel'>
${key}
</a>
</h4>
</div>
<div id='${key}_panel' class='panel-collapse collapse in'>
<div class='panel-body list-group-item'>
${makeContent(data[key])}
</div>
</div>
`;
}
if (allEmpty) {
$('#' + id).append('<div class="alert alert-danger" role="alert"><p>' + "No results..." + '</p></div>');
} else {
$('#' + id).append(content);
}
}
function search(e) {
e.preventDefault();
query = $("#searchbar").val()
if (!query) {
// Nothing to search...
console.log("Search phrase empty, won't do search")
return;
}
var selPacks = [];
$("#selPackages :selected").each(function () {
var id = this.value;
selPacks.push(id);
});
if (selPacks.length < 1) {
console.log("No package selected, won't do search")
return;
}
var mode = $('#mode-button').text().trim().toLowerCase();
var par = {};
par['packages'] = selPacks;
par['search'] = query;
par['mode'] = mode;
console.log(par);
var queryString = $.param(par, true);
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}");
$("#results").empty();
$.getJSON("{{ SERVER_BASE }}/search?" + queryString, function (result) {
handleSearchResponse("results", result);
$("#loading").empty();
}).fail(function (d) {
$("#loading").empty();
console.log(d.responseText);
handleError(JSON.parse(d.responseText));
});
}
$(function () {
@ -71,44 +169,17 @@
placement: "top",
title: tooltips[key]
});
$('#' + key).on('click', modeDropdownClicked);
});
$("#selPackages").selectpicker();
$("#search-button").on("click", search);
$("#search-button").on("click", function(e) {
e.preventDefault();
if (!hasValue("searchbar")) {
return;
}
var query = $("#searchbar").val();
selPackages = [];
var selPacks = $("#selPackages :selected");
selPacks.each(function () {
var id = this.value;
selPackages.push(id);
});
var par = {};
par['packages'] = selPackages;
par['search'] = query;
var queryString = $.param(par);
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}");
$("#results").empty();
$.getJSON("{{ SERVER_BASE }}/search?" + queryString, function (result) {
makeSearchList("#results", result);
$("#loading").empty();
}).fail(function (d) {
handleError(JSON.parse(d.responseText));
});
});
});
{% if search_result %}
handleSearchResponse("results", {{ search_result|safe }});
{% endif %}
</script>
{% endblock content %}

View File

@ -5,7 +5,7 @@ from epdb.models import Compound, User, CompoundStructure
class CompoundTest(TestCase):
fixtures = ["test_fixture.json.gz"]
fixtures = ["test_fixture.cleaned.json"]
def setUp(self):
pass
@ -53,6 +53,14 @@ class CompoundTest(TestCase):
description='No Desc'
)
with self.assertRaises(ValueError):
_ = Compound.create(
self.package,
smiles=' ',
name='Afoxolaner',
description='No Desc'
)
def test_smiles_are_trimmed(self):
c = Compound.create(
self.package,

View File

@ -0,0 +1,196 @@
from django.test import TestCase
from epdb.logic import PackageManager
from epdb.models import Compound, User, CompoundStructure, Reaction, Rule
class ReactionTest(TestCase):
fixtures = ["test_fixture.cleaned.json"]
def setUp(self):
pass
@classmethod
def setUpClass(cls):
super(ReactionTest, cls).setUpClass()
cls.user = User.objects.get(username='anonymous')
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
@classmethod
def tearDownClass(cls):
pass
def tearDown(self):
pass
def test_smoke(self):
educt = Compound.create(
self.package,
smiles='C(CCl)Cl',
name='1,2-Dichloroethane',
description='Eawag BBD compound c0001'
).default_structure
product = Compound.create(
self.package,
smiles='C(CO)Cl',
name='2-Chloroethanol',
description='Eawag BBD compound c0005'
).default_structure
r = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=[educt],
products=[product],
multi_step=False
)
self.assertEqual(r.smirks(), 'C(CCl)Cl>>C(CO)Cl')
self.assertEqual(r.name, 'Eawag BBD reaction r0001')
self.assertEqual(r.description, 'no description')
def test_string_educts_and_products(self):
r = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=['C(CCl)Cl'],
products=['C(CO)Cl'],
multi_step=False
)
self.assertEqual(r.smirks(), 'C(CCl)Cl>>C(CO)Cl')
def test_missing_smiles(self):
educt = Compound.create(
self.package,
smiles='C(CCl)Cl',
name='1,2-Dichloroethane',
description='Eawag BBD compound c0001'
).default_structure
product = Compound.create(
self.package,
smiles='C(CO)Cl',
name='2-Chloroethanol',
description='Eawag BBD compound c0005'
).default_structure
with self.assertRaises(ValueError):
_ = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=[educt],
products=[],
multi_step=False
)
with self.assertRaises(ValueError):
_ = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=[],
products=[product],
multi_step=False
)
with self.assertRaises(ValueError):
_ = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=[],
products=[],
multi_step=False
)
def test_empty_name_and_description_are_ignored(self):
r = Reaction.create(
package=self.package,
name='',
description='',
educts=['C(CCl)Cl'],
products=['C(CO)Cl'],
multi_step=False,
)
self.assertEqual(r.name, 'no name')
self.assertEqual(r.description, 'no description')
def test_deduplication(self):
rule = Rule.create(
package=self.package,
rule_type='SimpleAmbitRule',
name='bt0022-2833',
description='Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative',
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
)
r1 = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=['C(CCl)Cl'],
products=['C(CO)Cl'],
rules=[rule],
multi_step=False
)
r2 = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=['C(CCl)Cl'],
products=['C(CO)Cl'],
rules=[rule],
multi_step=False
)
# Check if create detects that this Compound already exist
# In this case the existing object should be returned
self.assertEqual(r1.pk, r2.pk)
self.assertEqual(len(self.package.reactions), 1)
def test_deduplication_without_rules(self):
r1 = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=['C(CCl)Cl'],
products=['C(CO)Cl'],
multi_step=False
)
r2 = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=['C(CCl)Cl'],
products=['C(CO)Cl'],
multi_step=False
)
# Check if create detects that this Compound already exist
# In this case the existing object should be returned
self.assertEqual(r1.pk, r2.pk)
self.assertEqual(len(self.package.reactions), 1)
def test_wrong_smiles(self):
with self.assertRaises(ValueError):
_ = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=['ASDF'],
products=['C(CO)Cl'],
multi_step=False
)
def test_delete(self):
r = Reaction.create(
package=self.package,
name='Eawag BBD reaction r0001',
educts=['C(CCl)Cl'],
products=['C(CO)Cl'],
multi_step=False
)
r.delete()
self.assertEqual(Reaction.objects.filter(package=self.package).count(), 0)

116
tests/test_rule_model.py Normal file
View File

@ -0,0 +1,116 @@
from django.test import TestCase
from epdb.logic import PackageManager
from epdb.models import Rule, User
class RuleTest(TestCase):
fixtures = ["test_fixture.cleaned.json"]
def setUp(self):
pass
@classmethod
def setUpClass(cls):
super(RuleTest, cls).setUpClass()
cls.user = User.objects.get(username='anonymous')
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
@classmethod
def tearDownClass(cls):
pass
def tearDown(self):
pass
def test_smoke(self):
r = Rule.create(
rule_type='SimpleAmbitRule',
package=self.package,
name='bt0022-2833',
description='Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative',
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
)
self.assertEqual(r.smirks,
'[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]')
self.assertEqual(r.name, 'bt0022-2833')
self.assertEqual(r.description,
'Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative')
def test_smirks_are_trimmed(self):
r = Rule.create(
rule_type='SimpleAmbitRule',
package=self.package,
name='bt0022-2833',
description='Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative',
smirks=' [H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4] ',
)
self.assertEqual(r.smirks,
'[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]')
def test_name_and_description_optional(self):
r = Rule.create(
rule_type='SimpleAmbitRule',
package=self.package,
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
)
self.assertRegex(r.name, 'Rule \\d+')
self.assertEqual(r.description, 'no description')
def test_empty_name_and_description_are_ignored(self):
r = Rule.create(
rule_type='SimpleAmbitRule',
package=self.package,
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
name='',
description='',
)
self.assertRegex(r.name, 'Rule \\d+')
self.assertEqual(r.description, 'no description')
def test_deduplication(self):
r1 = Rule.create(
rule_type='SimpleAmbitRule',
package=self.package,
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
name='',
description='',
)
r2 = Rule.create(
rule_type='SimpleAmbitRule',
package=self.package,
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
name='',
description='',
)
self.assertEqual(r1.pk, r2.pk)
self.assertEqual(len(self.package.rules), 1)
def test_valid_smirks(self):
with self.assertRaises(ValueError):
r = Rule.create(
rule_type='SimpleAmbitRule',
package=self.package,
smirks='This is not a valid SMIRKS',
name='',
description='',
)
def test_delete(self):
r = Rule.create(
rule_type='SimpleAmbitRule',
package=self.package,
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
name='',
description='',
)
r.delete()
self.assertEqual(Rule.objects.filter(package=self.package).count(), 0)

View File

@ -228,6 +228,14 @@ class FormatConverter(object):
#
# return list(res)
@staticmethod
def is_valid_smirks(smirks: str) -> bool:
try:
rdChemReactions.ReactionFromSmarts(smirks)
return True
except:
return False
@staticmethod
def apply(smiles: str, smirks: str, preprocess_smiles: bool = True, bracketize: bool = False,
standardize: bool = True, kekulize: bool = True) -> List['ProductSet']: