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

View File

@ -7,13 +7,21 @@ from django.db import transaction
from django.conf import settings as s from django.conf import settings as s
from epdb.models import User, Package, UserPackagePermission, GroupPackagePermission, Permission, Group, Setting, \ 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__) logger = logging.getLogger(__name__)
class UserManager(object): 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 @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 # avoid circular import :S
from .tasks import send_registration_mail from .tasks import send_registration_mail
@ -34,6 +42,17 @@ class UserManager(object):
# send email for verification # send email for verification
send_registration_mail.delay(u.pk) 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 return u
@staticmethod @staticmethod
@ -54,7 +73,16 @@ class UserManager(object):
uuid = user_url.strip().split('/')[-1] uuid = user_url.strip().split('/')[-1]
return get_user_model().objects.get(uuid=uuid) 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): 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 @staticmethod
def create_group(current_user, name, description): def create_group(current_user, name, description):
@ -110,9 +138,17 @@ class GroupManager(object):
group.save() group.save()
@staticmethod
def writable(user, group):
return (user == group.owner) or user.is_superuser
class PackageManager(object): 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 @staticmethod
def get_reviewed_packages(): def get_reviewed_packages():
@ -120,7 +156,6 @@ class PackageManager(object):
@staticmethod @staticmethod
def readable(user, package): def readable(user, package):
# TODO Owner!
if UserPackagePermission.objects.filter(package=package, user=user).exists() or \ if UserPackagePermission.objects.filter(package=package, user=user).exists() or \
GroupPackagePermission.objects.filter(package=package, group__in=GroupManager.get_groups(user)) or \ GroupPackagePermission.objects.filter(package=package, group__in=GroupManager.get_groups(user)) or \
package.reviewed is True or \ package.reviewed is True or \
@ -131,14 +166,21 @@ class PackageManager(object):
@staticmethod @staticmethod
def writable(user, package): def writable(user, package):
# TODO Owner! if UserPackagePermission.objects.filter(package=package, user=user, permission=Permission.WRITE[0]).exists() or \
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[0]).exists() or \
GroupPackagePermission.objects.filter(package=package, group__in=GroupManager.get_groups(user), UserPackagePermission.objects.filter(package=package, user=user, permission=Permission.ALL[0]).exists() or \
permission=Permission.WRITE) or \
user.is_superuser: user.is_superuser:
return True return True
return False 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 @staticmethod
def get_package_by_url(user, package_url): def get_package_by_url(user, package_url):
match = re.findall(PackageManager.package_pattern, package_url) match = re.findall(PackageManager.package_pattern, package_url)
@ -229,8 +271,12 @@ class PackageManager(object):
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def update_permissions(caller: User, package: Package, grantee: Union[User, Group], new_perm: Optional[str]): def update_permissions(caller: User, package: Package, grantee: Union[User, Group], new_perm: Optional[str]):
if not PackageManager.writable(caller, package): caller_perm = None
raise ValueError(f"User {caller} is not allowed to modify permissions on {package}") 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 = { data = {
'package': package, 'package': package,
@ -629,8 +675,6 @@ class PackageManager(object):
return pack return pack
class SettingManager(object): 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}$") 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): def get_setting_by_id(user, setting_id):
s = Setting.objects.get(uuid=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 return s
raise ValueError( raise ValueError(
@ -697,6 +742,116 @@ class SettingManager(object):
def set_default_setting(user: User, setting: Setting): def set_default_setting(user: User, setting: Setting):
pass 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): class SNode(object):
@ -719,7 +874,7 @@ class SNode(object):
class SEdge(object): class SEdge(object):
def __init__(self, educts: Union[SNode, List[SNode]], products: Union[SNode | List[SNode]], 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): if not isinstance(educts, list):
educts = [educts] educts = [educts]
@ -727,6 +882,7 @@ class SEdge(object):
self.educts = educts self.educts = educts
self.products = products self.products = products
self.rule = rule self.rule = rule
self.probability = probability
def __hash__(self): def __hash__(self):
full_hash = 0 full_hash = 0
@ -799,11 +955,45 @@ class SPathway(object):
elif isinstance(n, SNode): elif isinstance(n, SNode):
self.root_nodes.append(n) 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.smiles_to_node: Dict[str, SNode] = dict(**{n.smiles: n for n in self.root_nodes})
self.edges: Set['SEdge'] = set() self.edges: Set['SEdge'] = set()
self.done = False 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): def num_nodes(self):
return len(self.smiles_to_node.keys()) return len(self.smiles_to_node.keys())
@ -830,8 +1020,18 @@ class SPathway(object):
return sorted(res, key=lambda x: hash(x)) 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 = self._get_nodes_for_depth(from_depth) 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 new_tp = False
if substrates: if substrates:
@ -849,13 +1049,19 @@ class SPathway(object):
node = self.smiles_to_node[c] node = self.smiles_to_node[c]
cand_nodes.append(node) 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) 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 self.done = True
# Check if we need to write back data to database
if new_tp and self.persist: if new_tp and self.persist:
self._sync_to_pathway() self._sync_to_pathway()
# call save to update internal modified field
self.persist.save()
def _sync_to_pathway(self): def _sync_to_pathway(self):
logger.info("Updating Pathway with SPathway") logger.info("Updating Pathway with SPathway")
@ -876,6 +1082,11 @@ class SPathway(object):
product_nodes.append(self.snode_persist_lookup[snode]) product_nodes.append(self.snode_persist_lookup[snode])
e = Edge.create(self.persist, educt_nodes, product_nodes, sedge.rule) 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 self.sedge_persist_lookup[sedge] = e
logger.info("Update done!") logger.info("Update done!")

View File

@ -13,12 +13,14 @@ class Command(BaseCommand):
def create_users(self): def create_users(self):
if not User.objects.filter(email='anon@lorsba.ch').exists(): 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: else:
anon = User.objects.get(email='anon@lorsba.ch') anon = User.objects.get(email='anon@lorsba.ch')
if not User.objects.filter(email='admin@lorsba.ch').exists(): 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_staff = True
admin.is_superuser = True admin.is_superuser = True
admin.save() admin.save()
@ -26,6 +28,9 @@ class Command(BaseCommand):
admin = User.objects.get(email='admin@lorsba.ch') admin = User.objects.get(email='admin@lorsba.ch')
g = GroupManager.create_group(admin, 'enviPath Users', 'All enviPath Users') g = GroupManager.create_group(admin, 'enviPath Users', 'All enviPath Users')
g.public = True
g.save()
g.user_member.add(anon) g.user_member.add(anon)
g.save() g.save()
@ -36,7 +41,8 @@ class Command(BaseCommand):
admin.save() admin.save()
if not User.objects.filter(email='jebus@lorsba.ch').exists(): 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_staff = True
jebus.is_superuser = True jebus.is_superuser = True
jebus.save() jebus.save()
@ -94,6 +100,9 @@ class Command(BaseCommand):
setting.make_global_default() setting.make_global_default()
for u in [anon, jebus]: for u in [anon, jebus]:
u.default_setting = setting
u.save()
usp = UserSettingPermission() usp = UserSettingPermission()
usp.user = u usp.user = u
usp.setting = setting usp.setting = setting
@ -119,7 +128,7 @@ class Command(BaseCommand):
X, y = ml_model.build_dataset() X, y = ml_model.build_dataset()
ml_model.build_model(X, y) ml_model.build_model(X, y)
ml_model.evaluate_model() # ml_model.evaluate_model()
# If available create EnviFormerModel # If available create EnviFormerModel
if s.ENVIFORMER_PRESENT: 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.auth.models import AbstractUser
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db import models, transaction 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 import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
@ -43,8 +43,6 @@ class User(AbstractUser):
on_delete=models.SET_NULL, related_name='default_group') on_delete=models.SET_NULL, related_name='default_group')
default_setting = models.ForeignKey('epdb.Setting', on_delete=models.SET_NULL, default_setting = models.ForeignKey('epdb.Setting', on_delete=models.SET_NULL,
verbose_name='The users default settings', null=True, blank=False) verbose_name='The users default settings', null=True, blank=False)
# TODO remove
groups = models.ManyToManyField("Group", verbose_name='groups')
USERNAME_FIELD = "email" USERNAME_FIELD = "email"
REQUIRED_FIELDS = ['username'] 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) 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') name = models.TextField(blank=False, null=False, verbose_name='Group name')
owner = models.ForeignKey("User", verbose_name='Group Owner', on_delete=models.CASCADE) 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') 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') 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') 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 @transaction.atomic
def create(package: Package, smiles: str, name: str = None, description: str = None, *args, **kwargs) -> 'Compound': 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') raise ValueError('SMILES is required')
smiles = smiles.strip() smiles = smiles.strip()
@ -338,12 +337,14 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin):
c = Compound() c = Compound()
c.package = package c.package = package
# For name and description we have defaults so only set them if they carry a real value if name is None or name.strip() == '':
if name is not None and name != '': name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
c.name = name
if description is not None and description != '': c.name = name
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() c.save()
@ -403,25 +404,27 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin):
class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin): class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin):
compound = models.ForeignKey('epdb.Compound', on_delete=models.CASCADE, db_index=True) compound = models.ForeignKey('epdb.Compound', on_delete=models.CASCADE, db_index=True)
smiles = models.TextField(blank=False, null=False, verbose_name='SMILES') 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) 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 @property
def url(self): def url(self):
return '{}/structure/{}'.format(self.compound.url, self.uuid) 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 @staticmethod
@transaction.atomic @transaction.atomic
def create(compound: Compound, smiles: str, name: str = None, description: str = None, *args, **kwargs): 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 return cs
# TODO add find method
@property @property
def InChIKey(self): def as_svg(self, width: int = 800, height: int = 400):
return FormatConverter.InChIKey(self.smiles) return IndigoUtils.mol_to_svg(self.smiles, width=width, height=height)
@property
def canonical_smiles(self):
return FormatConverter.canonicalize(self.smiles)
@property
def as_svg(self):
return IndigoUtils.mol_to_svg(self.smiles)
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin): class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
@ -492,19 +485,9 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def create(package: Package, rule_type: str, name: str = None, description: str = None, *args, **kwargs): def create(rule_type: str, *args, **kwargs):
r = Rule.cls_for_type(rule_type)() cls = Rule.cls_for_type(rule_type)
r.package = package return cls.create(*args, **kwargs)
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
# #
# @property # @property
@ -533,6 +516,54 @@ class SimpleAmbitRule(SimpleRule):
reactant_filter_smarts = models.TextField(null=True, verbose_name='Reactant Filter SMARTS') reactant_filter_smarts = models.TextField(null=True, verbose_name='Reactant Filter SMARTS')
product_filter_smarts = models.TextField(null=True, verbose_name='Product 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 @property
def url(self): def url(self):
return '{}/simple-ambit-rule/{}'.format(self.package.url, self.uuid) 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, def create(package: Package, name: str = None, description: str = None,
educts: Union[List[str], List[CompoundStructure]] = None, educts: Union[List[str], List[CompoundStructure]] = None,
products: 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 = [] _educts = []
_products = [] _products = []
@ -662,18 +693,61 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin):
_products += products _products += products
else: 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 = Reaction()
r.package = package r.package = package
r.name = name
r.description = description 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.multi_step = multi_step
r.save() r.save()
if rule: if rules:
r.rules.add(rule) for rule in rules:
r.rules.add(rule)
for educt in _educts: for educt in _educts:
r.educts.add(educt) r.educts.add(educt)
@ -700,6 +774,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin):
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin): class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey('epdb.Package', verbose_name='Package', on_delete=models.CASCADE, db_index=True) 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 @property
def root_nodes(self): def root_nodes(self):
@ -709,6 +784,12 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
def nodes(self): def nodes(self):
return Node.objects.filter(pathway=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 @property
def edges(self): def edges(self):
return Edge.objects.filter(pathway=self) return Edge.objects.filter(pathway=self)
@ -717,6 +798,26 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
def url(self): def url(self):
return '{}/pathway/{}'.format(self.package.url, self.uuid) 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): def d3_json(self):
# Ideally it would be something like this but # Ideally it would be something like this but
# to reduce crossing in edges do a DFS # to reduce crossing in edges do a DFS
@ -770,7 +871,11 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
new_link = { new_link = {
'name': link['name'], 'name': link['name'],
'id': link['id'], 'id': link['id'],
'url': link['url'],
'image': link['image'],
'reaction': link['reaction'], 'reaction': link['reaction'],
'reaction_probability': link['reaction_probability'],
'scenarios': link['scenarios'],
'source': node_url_to_idx[link['start_node_urls'][0]], 'source': node_url_to_idx[link['start_node_urls'][0]],
'target': pseudo_idx 'target': pseudo_idx
} }
@ -781,7 +886,11 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
new_link = { new_link = {
'name': link['name'], 'name': link['name'],
'id': link['id'], 'id': link['id'],
'url': link['url'],
'image': link['image'],
'reaction': link['reaction'], 'reaction': link['reaction'],
'reaction_probability': link['reaction_probability'],
'scenarios': link['scenarios'],
'source': pseudo_idx, 'source': pseudo_idx,
'target': node_url_to_idx[target] 'target': node_url_to_idx[target]
} }
@ -797,9 +906,9 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
"completed": "true", "completed": "true",
"description": self.description, "description": self.description,
"id": self.url, "id": self.url,
"isIncremental": False, "isIncremental": self.kv.get('mode') == 'incremental',
"isPredicted": False, "isPredicted": self.kv.get('mode') == 'predicted',
"lastModified": 1447842835894, "lastModified": self.modified.strftime('%Y-%m-%d %H:%M:%S'),
"pathwayName": self.name, "pathwayName": self.name,
"reviewStatus": "reviewed" if self.package.reviewed else 'unreviewed', "reviewStatus": "reviewed" if self.package.reviewed else 'unreviewed',
"scenarios": [], "scenarios": [],
@ -813,18 +922,38 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
@staticmethod @staticmethod
@transaction.atomic @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 = Pathway()
pw.package = package pw.package = package
pw.name = name
pw.description = description
pw.save()
# create root node if name is None:
Node.create(pw, smiles, 0) 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 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): class Node(EnviPathModel, AliasMixin, ScenarioMixin):
pathway = models.ForeignKey('epdb.Pathway', verbose_name='belongs to', on_delete=models.CASCADE, db_index=True) 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 "imageSize": 490, # TODO
"name": self.default_node_label.name, "name": self.default_node_label.name,
"smiles": self.default_node_label.smiles, "smiles": self.default_node_label.smiles,
"scenarios": [{'name': s.name, 'url': s.url} for s in self.scenarios.all()],
} }
@staticmethod @staticmethod
def create(pathway, smiles, depth): def create(pathway: 'Pathway', smiles: str, depth: int, name: Optional[str] = None, description: Optional[str] = None):
c = Compound.create(pathway.package, smiles) c = Compound.create(pathway.package, smiles, name=name, description=description)
if Node.objects.filter(pathway=pathway, default_node_label=c.default_structure).exists(): 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) return Node.objects.get(pathway=pathway, default_node_label=c.default_structure)
n = Node() n = Node()
@ -886,34 +1015,21 @@ class Edge(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
return '{}/edge/{}'.format(self.pathway.url, self.uuid) return '{}/edge/{}'.format(self.pathway.url, self.uuid)
def d3_json(self): 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 { return {
'name': self.name, 'name': self.name,
'id': self.url, '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 # TODO
'start_node_urls': [x.url for x in self.start_nodes.all()], 'start_node_urls': [x.url for x in self.start_nodes.all()],
'end_node_urls': [x.url for x in self.end_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 @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): description: Optional[str] = None):
e = Edge() e = Edge()
e.pathway = pathway e.pathway = pathway
@ -934,13 +1050,17 @@ class Edge(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
r = Reaction.create(pathway.package, name=name, description=description, r = Reaction.create(pathway.package, name=name, description=description,
educts=[n.default_node_label for n in e.start_nodes.all()], educts=[n.default_node_label for n in e.start_nodes.all()],
products=[n.default_node_label for n in e.end_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.edge_label = r
e.save() e.save()
return e return e
@property
def as_svg(self):
return self.edge_label.as_svg if self.edge_label else None
class EPModel(PolymorphicModel, EnviPathModel): class EPModel(PolymorphicModel, EnviPathModel):
package = models.ForeignKey('epdb.Package', verbose_name='Package', on_delete=models.CASCADE, db_index=True) 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) eval_results = JSONField(null=True, blank=True, default=dict)
def status(self):
return self.PROGRESS_STATUS_CHOICES[self.model_status]
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def create(package, name, description, rule_packages, data_packages, eval_packages, threshold): def create(package, name, description, rule_packages, data_packages, eval_packages, threshold):
@ -1201,7 +1324,7 @@ class MLRelativeReasoning(EPModel):
self.save() self.save()
mod = SparseLabelECC( mod = SparseLabelECC(
**s.DEFAULT_MODELS_PARAMS **s.DEFAULT_DT_MODEL_PARAMS
) )
mod.fit(X, y) mod.fit(X, y)
@ -1247,7 +1370,7 @@ class MLRelativeReasoning(EPModel):
y_train, y_test = y[train_index], y[test_index] y_train, y_test = y[train_index], y[test_index]
model = SparseLabelECC( model = SparseLabelECC(
**s.DEFAULT_MODELS_PARAMS **s.DEFAULT_DT_MODEL_PARAMS
) )
model.fit(X_train, y_train) 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) 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", 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, model = models.ForeignKey('EPModel', verbose_name='Setting EPModel', on_delete=models.SET_NULL, null=True,
blank=True) blank=True)
model_threshold = models.FloatField(null=True, blank=True, verbose_name='Setting Model Threshold', default=0.25) 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 @cached_property
def applicable_rules(self): def applicable_rules(self):
""" """

View File

@ -1,4 +1,6 @@
import logging import logging
from typing import Optional
from celery.signals import worker_process_init from celery.signals import worker_process_init
from celery import shared_task from celery import shared_task
from epdb.models import Pathway, Node, Edge, EPModel, Setting from epdb.models import Pathway, Node, Edge, EPModel, Setting
@ -40,11 +42,40 @@ def evaluate_model(model_pk: int):
@shared_task(queue='predict') @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) pw = Pathway.objects.get(id=pw_pk)
setting = Setting.objects.get(id=pred_setting_pk) setting = Setting.objects.get(id=pred_setting_pk)
spw = SPathway(prediction_setting=setting, persist=pw)
level = 0 pw.kv.update(**{'status': 'running'})
while not spw.done: pw.save()
spw.predict_step(from_depth=level)
level += 1 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 # Home
re_path(r'^$', v.index, name='index'), re_path(r'^$', v.index, name='index'),
# re_path(r'^login', v.login, name='login'),
# Top level urls # Top level urls
re_path(r'^package$', v.packages, name='packages'), re_path(r'^package$', v.packages, name='packages'),
re_path(r'^compound$', v.compounds, name='compounds'), 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$', 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'), re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})$', v.package_pathway, name='package pathway detail'),
# Pathway Nodes # 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'), 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 # 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$', 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/(?P<edge_uuid>{UUID})$', v.package_pathway_edge, name='package pathway edge detail'),
# Scenario # 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$', 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'), 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 json
import logging import logging
from functools import wraps
from typing import List, Dict, Any from typing import List, Dict, Any
from django.conf import settings as s 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.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from utilities.chem import FormatConverter, IndigoUtils 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, \ from .models import Package, GroupPackagePermission, Group, CompoundStructure, Compound, Reaction, Rule, Pathway, Node, \
EPModel, EnviFormer, MLRelativeReasoning, RuleBaseRelativeReasoning, Scenario, SimpleAmbitRule, APIToken, \ EPModel, EnviFormer, MLRelativeReasoning, RuleBaseRelativeReasoning, Scenario, SimpleAmbitRule, APIToken, \
UserPackagePermission, Permission, License, User UserPackagePermission, Permission, License, User, Edge
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def log_post_params(request): def log_post_params(request):
for k, v in request.POST.items(): if s.DEBUG:
logger.debug(f"{k}\t{v}") 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]: def get_base_context(request) -> Dict[str, Any]:
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
can_edit = editable(request, current_user)
ctx = { ctx = {
'title': 'enviPath', 'title': 'enviPath',
'meta': { 'meta': {
'version': '0.0.1', 'version': '0.0.1',
'server_url': s.SERVER_URL, 'server_url': s.SERVER_URL,
'user': current_user, 'user': current_user,
'can_edit': can_edit,
'readable_packages': PackageManager.get_all_readable_packages(current_user, include_reviewed=True), 'readable_packages': PackageManager.get_all_readable_packages(current_user, include_reviewed=True),
'writeable_packages': PackageManager.get_all_writeable_packages(current_user), 'writeable_packages': PackageManager.get_all_writeable_packages(current_user),
'available_groups': GroupManager.get_groups(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 return bread
# @catch_exceptions
def index(request): def index(request):
current_user = _anonymous_or_real(request)
context = get_base_context(request) context = get_base_context(request)
context['title'] = 'enviPath - Home' context['title'] = 'enviPath - Home'
context['meta']['current_package'] = context['meta']['user'].default_package context['meta']['current_package'] = context['meta']['user'].default_package
@ -77,6 +121,16 @@ def index(request):
return render(request, 'index/index.html', context) 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): def packages(request):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -86,9 +140,10 @@ def packages(request):
context['object_type'] = 'package' context['object_type'] = 'package'
context['meta']['current_package'] = context['meta']['user'].default_package context['meta']['current_package'] = context['meta']['user'].default_package
context['meta']['can_edit'] = True
reviewed_package_qs = Package.objects.filter(reviewed=True) reviewed_package_qs = Package.objects.filter(reviewed=True).order_by('created')
unreviewed_package_qs = PackageManager.get_all_readable_packages(current_user) unreviewed_package_qs = PackageManager.get_all_readable_packages(current_user).order_by('name')
context['reviewed_objects'] = reviewed_package_qs context['reviewed_objects'] = reviewed_package_qs
context['unreviewed_objects'] = unreviewed_package_qs context['unreviewed_objects'] = unreviewed_package_qs
@ -103,7 +158,6 @@ def packages(request):
else: else:
package_name = request.POST.get('package-name') package_name = request.POST.get('package-name')
package_description = request.POST.get('package-description', s.DEFAULT_VALUES['description']) 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) created_package = PackageManager.create_package(current_user, package_name, package_description)
@ -342,7 +396,23 @@ def models(request):
def search(request): def search(request):
current_user = _anonymous_or_real(request)
if request.method == 'GET': 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 = get_base_context(request)
context['title'] = 'enviPath - Search' context['title'] = 'enviPath - Search'
@ -352,13 +422,21 @@ def search(request):
{'Home': s.SERVER_URL}, {'Home': s.SERVER_URL},
{'Search': s.SERVER_URL + '/search'}, {'Search': s.SERVER_URL + '/search'},
] ]
# TODO perm
reviewed_package_qs = Package.objects.filter(reviewed=True) reviewed_package_qs = PackageManager.get_reviewed_packages()
unreviewed_package_qs = Package.objects.filter(reviewed=False) unreviewed_package_qs = PackageManager.get_all_readable_packages(current_user)
context['reviewed_objects'] = reviewed_package_qs context['reviewed_objects'] = reviewed_package_qs
context['unreviewed_objects'] = unreviewed_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) 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) return render(request, 'objects/model.html', context)
if request.method == 'POST': elif request.method == 'POST':
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-model': if hidden == 'delete-model':
current_model.delete() current_model.delete()
@ -496,21 +574,9 @@ def package_model(request, package_uuid, model_uuid):
return HttpResponseBadRequest() return HttpResponseBadRequest()
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
#
# new_compound_name = request.POST.get('compound-name') else:
# new_compound_description = request.POST.get('compound-description') return HttpResponseBadRequest()
#
# 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()
def package(request, package_uuid): def package(request, package_uuid):
@ -803,8 +869,7 @@ def package_rules(request, package_uuid):
elif request.method == 'POST': elif request.method == 'POST':
for k, v in request.POST.items(): log_post_params(request)
print(k, v)
# Generic params # Generic params
rule_name = request.POST.get('rule-name') rule_name = request.POST.get('rule-name')
@ -817,8 +882,8 @@ def package_rules(request, package_uuid):
# Obtain parameters as required by rule type # Obtain parameters as required by rule type
if rule_type == 'SimpleAmbitRule': if rule_type == 'SimpleAmbitRule':
params['smirks'] = request.POST.get('rule-smirks') params['smirks'] = request.POST.get('rule-smirks')
params['reactant_smarts'] = request.POST.get('rule-reactant-smarts') params['reactant_filter_smarts'] = request.POST.get('rule-reactant-smarts')
params['product_smarts'] = request.POST.get('rule-product-smarts') params['product_filter_smarts'] = request.POST.get('rule-product-smarts')
elif rule_type == 'SimpleRDKitRule': elif rule_type == 'SimpleRDKitRule':
params['reaction_smarts'] = request.POST.get('rule-reaction-smarts') params['reaction_smarts'] = request.POST.get('rule-reaction-smarts')
elif rule_type == 'ParallelRule': elif rule_type == 'ParallelRule':
@ -828,7 +893,7 @@ def package_rules(request, package_uuid):
else: else:
return HttpResponseBadRequest() 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) return redirect(r.url)
else: else:
@ -854,7 +919,7 @@ def package_rule(request, package_uuid, rule_uuid):
else: # isinstance(current_rule, ParallelRule) or isinstance(current_rule, SequentialRule): else: # isinstance(current_rule, ParallelRule) or isinstance(current_rule, SequentialRule):
return render(request, 'objects/composite_rule.html', context) 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 := request.POST.get('hidden', None):
if hidden == 'delete-rule': if hidden == 'delete-rule':
current_rule.delete() current_rule.delete()
@ -862,8 +927,23 @@ def package_rule(request, package_uuid, rule_uuid):
else: else:
return HttpResponseBadRequest() 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 # https://envipath.org/package/<id>/reaction
def package_reactions(request, package_uuid): def package_reactions(request, package_uuid):
@ -912,6 +992,8 @@ def package_reactions(request, package_uuid):
return redirect(r.url) return redirect(r.url)
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/reaction/<id> # https://envipath.org/package/<id>/reaction/<id>
def package_reaction(request, package_uuid, reaction_uuid): 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) return render(request, 'objects/reaction.html', context)
if request.method == 'POST': elif request.method == 'POST':
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-reaction': if hidden == 'delete-reaction':
current_reaction.delete() current_reaction.delete()
@ -954,6 +1036,8 @@ def package_reaction(request, package_uuid, reaction_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway # https://envipath.org/package/<id>/pathway
def package_pathways(request, package_uuid): def package_pathways(request, package_uuid):
@ -989,7 +1073,7 @@ def package_pathways(request, package_uuid):
return render(request, 'collections/objects_list.html', context) return render(request, 'collections/objects_list.html', context)
if request.method == 'POST': elif request.method == 'POST':
log_post_params(request) log_post_params(request)
@ -1002,15 +1086,34 @@ def package_pathways(request, package_uuid):
return HttpResponseBadRequest() return HttpResponseBadRequest()
stand_smiles = FormatConverter.standardize(smiles) 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() pred_setting = current_user.prediction_settings()
pw.setting = pred_setting
pw.save()
from .tasks import predict from .tasks import predict
predict.delay(pw.pk, pred_setting.pk) predict.delay(pw.pk, pred_setting.pk, limit=limit)
return redirect(pw.url) return redirect(pw.url)
else:
return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway/<id> # https://envipath.org/package/<id>/pathway/<id>
def package_pathway(request, package_uuid, pathway_uuid): 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, 'objects/pathway.html', context)
# return render(request, 'pathway_playground2.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 := request.POST.get('hidden', None):
if hidden == 'delete-pathway': if hidden == 'delete-pathway':
current_pathway.delete() current_pathway.delete()
@ -1051,30 +1154,92 @@ def package_pathway(request, package_uuid, pathway_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
pathway_name = request.POST.get('pathway-name')
pathway_description = request.POST.get('pathway-description')
# if any([pathway_name, pathway_description]):
# if pathway_name is not None and pathway_name.strip() != '':
# pathway_name = pathway_name.strip()
# def package_relative_reasonings(request, package_id):
# if request.method == 'GET': current_pathway.name = pathway_name
# pass
# if pathway_description is not None and pathway_description.strip() != '':
# pathway_description = pathway_description.strip()
# def package_relative_reasoning(request, package_id, relative_reasoning_id):
# current_user = _anonymous_or_real(request) current_pathway.description = pathway_description
#
# if request.method == 'GET': current_pathway.save()
# pass return redirect(current_pathway.url)
# elif request.method == 'POST':
# pass node_url = request.POST.get('node')
#
# # if node_url:
# # n = current_pathway.get_node(node_url)
# # # https://envipath.org/package/<id>/pathway/<id>/node
# # def package_pathway_nodes(request, package_id, pathway_id): from .tasks import predict
# # pass # 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> # 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 svg_data = current_node.as_svg
return HttpResponse(svg_data, content_type="image/svg+xml") 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 # https://envipath.org/package/<id>/scenario
def package_scenarios(request, package_uuid): def package_scenarios(request, package_uuid):
current_user = _anonymous_or_real(request) 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) return render(request, 'objects/scenario.html', context)
### END UNTESTED
############## ##############
# User/Group # # User/Group #
############## ##############
@ -1201,7 +1471,7 @@ def users(request):
return render(request, 'errors/user_account_inactive.html', status=403) return render(request, 'errors/user_account_inactive.html', status=403)
email = temp_user.email email = temp_user.email
except get_user_model().DoesNotExists: except get_user_model().DoesNotExist:
return HttpResponseBadRequest() return HttpResponseBadRequest()
user = authenticate(username=email, password=password) user = authenticate(username=email, password=password)
@ -1289,6 +1559,18 @@ def user(request, user_uuid):
logout(request) logout(request)
return redirect(s.SERVER_URL) 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_model_pk = request.POST.get('model')
prediction_threshold = request.POST.get('threshold') prediction_threshold = request.POST.get('threshold')
prediction_max_nodes = request.POST.get('max_nodes') prediction_max_nodes = request.POST.get('max_nodes')
@ -1509,3 +1791,9 @@ def layout(request):
def depict(request): def depict(request):
if smiles := request.GET.get('smiles'): if smiles := request.GET.get('smiles'):
return HttpResponse(IndigoUtils.mol_to_svg(smiles), content_type='image/svg+xml') 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>"; + "</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){ function fillPRCurve(modelUri, onclick){
if (modelUri == '') { if (modelUri == '') {
return; return;

View File

@ -1,5 +1,18 @@
console.log("loaded") 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 }}; // data = {{ pathway.d3_json | safe }};
// elem = 'vizdiv' // elem = 'vizdiv'
function draw(pathway, elem) { function draw(pathway, elem) {
@ -48,7 +61,7 @@ function draw(pathway, elem) {
const avgY = d3.mean(childNodes, d => d.y); const avgY = d3.mean(childNodes, d => d.y);
n.fx = avgX; n.fx = avgX;
// keep level as is // keep level as is
n.fy = n.y; n.fy = n.y;
} }
} }
}); });
@ -77,8 +90,100 @@ function draw(pathway, elem) {
d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted")); 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"); const zoomable = d3.select("#zoomable");
@ -102,10 +207,10 @@ function draw(pathway, elem) {
orig_depth = n.depth orig_depth = n.depth
// console.log(n.id, parents) // console.log(n.id, parents)
for(idx in parents) { for (idx in parents) {
p = nodes[parents[idx]] p = nodes[parents[idx]]
// console.log(p.depth) // console.log(p.depth)
if(p.depth >= n.depth) { if (p.depth >= n.depth) {
// keep the .5 steps for pseudo nodes // keep the .5 steps for pseudo nodes
n.depth = n.pseudo ? p.depth + 1 : Math.floor(p.depth + 1); n.depth = n.pseudo ? p.depth + 1 : Math.floor(p.depth + 1);
// console.log("Adjusting", orig_depth, Math.floor(p.depth + 1)); // console.log("Adjusting", orig_depth, Math.floor(p.depth + 1));
@ -128,13 +233,15 @@ function draw(pathway, elem) {
.enter().append("line") .enter().append("line")
// Check if target is pseudo and draw marker only if not pseudo // Check if target is pseudo and draw marker only if not pseudo
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link") .attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
.on("mouseover", (event, d) => { // .on("mouseover", (event, d) => {
tooltip.style("visibility", "visible") // tooltip.style("visibility", "visible")
.text(`Link: ${d.source.id}${d.target.id}`) // .text(`Link: ${d.source.id} → ${d.target.id}`)
.style("top", `${event.pageY + 5}px`) // .style("top", `${event.pageY + 5}px`)
.style("left", `${event.pageX + 5}px`); // .style("left", `${event.pageX + 5}px`);
}) // })
.on("mouseout", () => tooltip.style("visibility", "hidden")); // .on("mouseout", () => tooltip.style("visibility", "hidden"));
pop_add(link, "Reaction", edge_popup);
// Knoten zeichnen // Knoten zeichnen
const node = zoomable.append("g") const node = zoomable.append("g")
@ -145,19 +252,19 @@ function draw(pathway, elem) {
.on("start", dragstarted) .on("start", dragstarted)
.on("drag", dragged) .on("drag", dragged)
.on("end", dragended)) .on("end", dragended))
.on("click", function(event, d) { .on("click", function (event, d) {
d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted")); d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
}) })
.on("mouseover", (event, d) => { // .on("mouseover", (event, d) => {
if (d.pseudo) { // if (d.pseudo) {
return // return
} // }
tooltip.style("visibility", "visible") // tooltip.style("visibility", "visible")
.text(`Node: ${d.id} Depth: ${d.depth}`) // .text(`Node: ${d.id} Depth: ${d.depth}`)
.style("top", `${event.pageY + 5}px`) // .style("top", `${event.pageY + 5}px`)
.style("left", `${event.pageX + 5}px`); // .style("left", `${event.pageX + 5}px`);
}) // })
.on("mouseout", () => tooltip.style("visibility", "hidden")); // .on("mouseout", () => tooltip.style("visibility", "hidden"));
// Kreise für die Knoten hinzufügen // Kreise für die Knoten hinzufügen
node.append("circle") node.append("circle")
@ -172,4 +279,6 @@ function draw(pathway, elem) {
.attr("y", -nodeRadius) .attr("y", -nodeRadius)
.attr("width", nodeRadius * 2) .attr("width", nodeRadius * 2)
.attr("height", nodeRadius * 2); .attr("height", nodeRadius * 2);
pop_add(node, "Compound", node_popup);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,14 @@
<li> {% if meta.can_edit %}
<a role="button" data-toggle="modal" data-target="#edit_compound_modal"> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a> <a role="button" data-toggle="modal" data-target="#edit_compound_modal">
</li> <i class="glyphicon glyphicon-edit"></i> Edit Compound</a>
<li> </li>
<a role="button" data-toggle="modal" data-target="#add_structure_modal"> <li>
<i class="glyphicon glyphicon-plus"></i> Add Structure</a> <a role="button" data-toggle="modal" data-target="#add_structure_modal">
</li> <i class="glyphicon glyphicon-plus"></i> Add Structure</a>
<li> </li>
<a class="button" data-toggle="modal" data-target="#delete_compound_modal"> <li>
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a> <a class="button" data-toggle="modal" data-target="#delete_compound_modal">
</li> <i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
</li>
{% endif %}

View File

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

View File

@ -1,8 +1,10 @@
<li> {% if meta.can_edit %}
<a role="button" data-toggle="modal" data-target="#delete_group_modal"> <li>
<i class="glyphicon glyphicon-trash"></i> Delete Group</a> <a role="button" data-toggle="modal" data-target="#delete_group_modal">
</li> <i class="glyphicon glyphicon-trash"></i> Delete Group</a>
<li> </li>
<a role="button" data-toggle="modal" data-target="#edit_group_member_modal"> <li>
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a> <a role="button" data-toggle="modal" data-target="#edit_group_member_modal">
</li> <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,16 +1,18 @@
<li> {% if meta.can_edit %}
<a role="button" data-toggle="modal" data-target="#edit_package_modal"> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Package</a> <a role="button" data-toggle="modal" data-target="#edit_package_modal">
</li> <i class="glyphicon glyphicon-edit"></i> Edit Package</a>
<li> </li>
<a role="button" data-toggle="modal" data-target="#edit_package_permissions_modal"> <li>
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a> <a role="button" data-toggle="modal" data-target="#edit_package_permissions_modal">
</li> <i class="glyphicon glyphicon-user"></i> Edit Permissions</a>
<li> </li>
<a role="button" data-toggle="modal" data-target="#set_license_modal"> <li>
<i class="glyphicon glyphicon-duplicate"></i> License</a> <a role="button" data-toggle="modal" data-target="#set_license_modal">
</li> <i class="glyphicon glyphicon-duplicate"></i> License</a>
<li> </li>
<a class="button" data-toggle="modal" data-target="#delete_package_modal"> <li>
<i class="glyphicon glyphicon-trash"></i> Delete Package</a> <a class="button" data-toggle="modal" data-target="#delete_package_modal">
</li> <i class="glyphicon glyphicon-trash"></i> Delete Package</a>
</li>
{% endif %}

View File

@ -1,8 +1,32 @@
<li> {% if meta.can_edit %}
<a role="button" data-toggle="modal" data-target="#edit_pathway_modal"> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a> <a class="button" data-toggle="modal" data-target="#add_pathway_node_modal">
</li> <i class="glyphicon glyphicon-plus"></i> Add Compound</a>
<li> </li>
<a class="button" data-toggle="modal" data-target="#delete_pathawy_modal"> <li>
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a> <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">
</li> <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,8 +1,10 @@
<li> {% if meta.can_edit %}
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal"> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a> <a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
</li> <i class="glyphicon glyphicon-edit"></i> Edit Reaction</a>
<li> </li>
<a class="button" data-toggle="modal" data-target="#delete_reaction_modal"> <li>
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a> <a class="button" data-toggle="modal" data-target="#delete_reaction_modal">
</li> <i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
</li>
{% endif %}

View File

@ -1,4 +1,6 @@
<li> {% if meta.can_edit %}
<a role="button" data-toggle="modal" data-target="#edit_rule_modal"> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a> <a role="button" data-toggle="modal" data-target="#edit_rule_modal">
</li> <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,20 +1,22 @@
<li> {% if meta.can_edit %}
<a role="button" data-toggle="modal" data-target="#edit_user_modal"> <li>
<i class="glyphicon glyphicon-edit"></i> Update</a> <a role="button" data-toggle="modal" data-target="#edit_user_modal">
</li> <i class="glyphicon glyphicon-edit"></i> Update</a>
<li> </li>
<a role="button" data-toggle="modal" data-target="#edit_password_modal"> <li>
<i class="glyphicon glyphicon-lock"></i> Update Password</a> <a role="button" data-toggle="modal" data-target="#edit_password_modal">
</li> <i class="glyphicon glyphicon-lock"></i> Update Password</a>
<li> </li>
<a role="button" data-toggle="modal" data-target="#new_prediction_setting_modal"> <li>
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a> <a role="button" data-toggle="modal" data-target="#new_prediction_setting_modal">
</li> <i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a>
<li> </li>
<a role="button" data-toggle="modal" data-target="#manage_api_token_modal"> {# <li>#}
<i class="glyphicon glyphicon-console"></i> Manage API Token</a> {# <a role="button" data-toggle="modal" data-target="#manage_api_token_modal">#}
</li> {# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
<li> {# </li>#}
<a role="button" data-toggle="modal" data-target="#delete_user_modal"> <li>
<i class="glyphicon glyphicon-trash"></i> Delete Account</a> <a role="button" data-toggle="modal" data-target="#delete_user_modal">
</li> <i class="glyphicon glyphicon-trash"></i> Delete Account</a>
</li>
{% endif %}

View File

@ -35,12 +35,16 @@
{% include "modals/collections/new_reaction_modal.html" %} {% include "modals/collections/new_reaction_modal.html" %}
{% elif object_type == 'pathway' %} {% elif object_type == 'pathway' %}
{# {% include "modals/collections/new_pathway_modal.html" %} #} {# {% 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' %} {% elif object_type == 'scenario' %}
{% include "modals/collections/new_scenario_modal.html" %} {% include "modals/collections/new_scenario_modal.html" %}
{% elif object_type == 'model' %} {% elif object_type == 'model' %}
{% include "modals/collections/new_model_modal.html" %} {% include "modals/collections/new_model_modal.html" %}
{% elif object_type == 'setting' %} {% elif object_type == 'setting' %}
{% include "modals/collections/new_setting_modal.html" %} {#{% include "modals/collections/new_setting_modal.html" %}#}
{% elif object_type == 'user' %} {% elif object_type == 'user' %}
<div></div> <div></div>
{% elif object_type == 'group' %} {% elif object_type == 'group' %}
@ -63,6 +67,10 @@
Reactions Reactions
{% elif object_type == 'pathway' %} {% elif object_type == 'pathway' %}
Pathways Pathways
{% elif object_type == 'node' %}
Nodes
{% elif object_type == 'edge' %}
Edges
{% elif object_type == 'scenario' %} {% elif object_type == 'scenario' %}
Scenarios Scenarios
{% elif object_type == 'model' %} {% elif object_type == 'model' %}
@ -75,34 +83,38 @@
Groups Groups
{% endif %} {% endif %}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
style="padding-right:1em"></span></a> style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu"> <ul id="actionsList" class="dropdown-menu">
{% block actions %} {% block actions %}
{% if object_type == 'package' %} {% if object_type == 'package' %}
{% include "actions/collections/package.html" %} {% include "actions/collections/package.html" %}
{% elif object_type == 'compound' %} {% elif object_type == 'compound' %}
{% include "actions/collections/compound.html" %} {% include "actions/collections/compound.html" %}
{% elif object_type == 'structure' %} {% elif object_type == 'structure' %}
{% include "actions/collections/compound_structure.html" %} {% include "actions/collections/compound_structure.html" %}
{% elif object_type == 'rule' %} {% elif object_type == 'rule' %}
{% include "actions/collections/rule.html" %} {% include "actions/collections/rule.html" %}
{% elif object_type == 'reaction' %} {% elif object_type == 'reaction' %}
{% include "actions/collections/reaction.html" %} {% include "actions/collections/reaction.html" %}
{% elif object_type == 'setting' %} {% elif object_type == 'setting' %}
{% include "actions/collections/setting.html" %} {% include "actions/collections/setting.html" %}
{% elif object_type == 'scenario' %} {% elif object_type == 'scenario' %}
{% include "actions/collections/scenario.html" %} {% include "actions/collections/scenario.html" %}
{% elif object_type == 'model' %} {% elif object_type == 'model' %}
{% include "actions/collections/model.html" %} {% include "actions/collections/model.html" %}
{% elif object_type == 'pathway' %} {% elif object_type == 'pathway' %}
{% include "actions/collections/pathway.html" %} {% include "actions/collections/pathway.html" %}
{% elif object_type == 'group' %} {% elif object_type == 'node' %}
{% include "actions/collections/group.html" %} {% include "actions/collections/node.html" %}
{% endif %} {% elif object_type == 'edge' %}
{% include "actions/collections/edge.html" %}
{% elif object_type == 'group' %}
{% include "actions/collections/group.html" %}
{% endif %}
{% endblock %} {% endblock %}
</ul> </ul>
</div> </div>
@ -133,6 +145,14 @@
<p>A pathway displays the (predicted) biodegradation of a compound as graph. <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 <a target="_blank" href="https://wiki.envipath.org/index.php/pathways" role="button">Learn more
&gt;&gt;</a></p> &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' %} {% elif object_type == 'scenario' %}
<p>A scenario contains meta-information that can be attached to other data (compounds, rules, ..). <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 <a target="_blank" href="https://wiki.envipath.org/index.php/scenarios" role="button">Learn more
@ -185,7 +205,7 @@
{% endfor %} {% endfor %}
{% else %} {% else %}
{% for obj in reviewed_objects|slice:":50" %} {% 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" <span class="glyphicon glyphicon-star" aria-hidden="true"
style="float:right" data-toggle="tooltip" style="float:right" data-toggle="tooltip"
data-placement="top" title="" data-original-title="Reviewed"> data-placement="top" title="" data-original-title="Reviewed">
@ -229,51 +249,52 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<script> <script>
$(function() { $(function () {
$('#modal-form-delete-submit').on('click', function (e) {
e.preventDefault();
$('#modal-form-delete').submit();
});
$('#object-search').show(); $('#modal-form-delete-submit').on('click', function (e) {
e.preventDefault();
if ($('#load-remaining').length) { $('#modal-form-delete').submit();
$('#load-remaining').on('click', function() {
makeLoadingGif("#load-all-loading", "{% static '/images/wait.gif' %}");
$('#load-all-error').hide();
$.getJSON('?all=true', function(resp) {
$('#ReviewedContent').empty();
$('#UnreviewedContent').empty();
for(o in resp.objects) {
obj = resp.objects[o];
if (obj.reviewed) {
$('#ReviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + '</a>');
} else {
$('#UnreviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + '</a>');
}
}
$('#load-all-loading').empty();
$('#load-remaining').hide();
}).fail(function(resp) {
$('#load-all-loading').empty();
$('#load-all-error').show();
}); });
});
}
$('#object-search').on('keyup', function() { $('#object-search').show();
let query = $(this).val().toLowerCase();
$('a.list-group-item').each(function() {
let text = $(this).text().toLowerCase();
$(this).toggle(text.indexOf(query) !== -1);
});
});
}); if ($('#load-remaining').length) {
</script> $('#load-remaining').on('click', function () {
makeLoadingGif("#load-all-loading", "{% static '/images/wait.gif' %}");
$('#load-all-error').hide();
$.getJSON('?all=true', function (resp) {
$('#ReviewedContent').empty();
$('#UnreviewedContent').empty();
for (o in resp.objects) {
obj = resp.objects[o];
if (obj.reviewed) {
$('#ReviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + '</a>');
} else {
$('#UnreviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + '</a>');
}
}
$('#load-all-loading').empty();
$('#load-remaining').hide();
}).fail(function (resp) {
$('#load-all-loading').empty();
$('#load-all-error').show();
});
});
}
$('#object-search').on('keyup', function () {
let query = $(this).val().toLowerCase();
$('a.list-group-item').each(function () {
let text = $(this).text().toLowerCase();
$(this).toggle(text.indexOf(query) !== -1);
});
});
});
</script>
{% endblock content %} {% endblock content %}

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 %} {% load static %}
{% block content %} {% 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> <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> us.</a>
</p> </p>
</div> </div>

View File

@ -14,13 +14,25 @@
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/css/bootstrap-select.min.css"> 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> <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/js/bootstrap-select.min.js"></script>
<!-- CDN END --> <!-- 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 #} {# Favicon #}
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/> <link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
<!-- {# C3 CSS #}--> <!-- {# C3 CSS #}-->
<!-- <link id="css-c3" href="{% static 'css/c3.css' %}" rel="stylesheet" type="text/css"/>--> <!-- <link id="css-c3" href="{% static 'css/c3.css' %}" rel="stylesheet" type="text/css"/>-->
<!-- {# EP CSS #}--> <!-- {# EP CSS #}-->
<!-- <link id="css-pps_white_general" href="{% static 'css/epp.css' %}" rel="stylesheet" type="text/css"/>--> <!-- <link id="css-pps_white_general" href="{% static 'css/epp.css' %}" rel="stylesheet" type="text/css"/>-->
{# General EP JS #} {# General EP JS #}
@ -29,21 +41,23 @@
<script src="{% static 'js/jquery-bootstrap-modal-steps.js' %}"></script> <script src="{% static 'js/jquery-bootstrap-modal-steps.js' %}"></script>
{% if not debug %} {% if not debug %}
<!-- Matomo --> <!-- Matomo -->
<script> <script>
var _paq = window._paq = window._paq || []; var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */ /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']); _paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']); _paq.push(['enableLinkTracking']);
(function() { (function () {
var u="//matomo.envipath.com/"; var u = "//matomo.envipath.com/";
_paq.push(['setTrackerUrl', u+'matomo.php']); _paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '7']); _paq.push(['setSiteId', '7']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; 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';
</script> s.parentNode.insertBefore(g, s);
<!-- End Matomo Code --> })();
</script>
<!-- End Matomo Code -->
{% endif %} {% endif %}
</head> </head>
@ -52,13 +66,13 @@
<div class="container-fluid"> <div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display --> <!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header navbar-header-framework"> <div class="navbar-header navbar-header-framework">
<!-- <button type="button" class="navbar-toggle navbar-toggle-framework" data-toggle="collapse"--> <!-- <button type="button" class="navbar-toggle navbar-toggle-framework" data-toggle="collapse"-->
<!-- data-target="#navbarCollapse">--> <!-- data-target="#navbarCollapse">-->
<!-- <span class="sr-only">Toggle navigation</span>--> <!-- <span class="sr-only">Toggle navigation</span>-->
<!-- <span class="icon-bar"></span>--> <!-- <span class="icon-bar"></span>-->
<!-- <span class="icon-bar"></span>--> <!-- <span class="icon-bar"></span>-->
<!-- <span class="icon-bar"></span>--> <!-- <span class="icon-bar"></span>-->
<!-- </button>--> <!-- </button>-->
<a id="pictureLink" href="{{ meta.server_url }}" class="navbar-brand"> <a id="pictureLink" href="{{ meta.server_url }}" class="navbar-brand">
<img id="image-logo-short-white.svg" src='{% static "/images/logo-short-white.svg" %}' width="100" <img id="image-logo-short-white.svg" src='{% static "/images/logo-short-white.svg" %}' width="100"
alt="enviPath"> alt="enviPath">
@ -95,16 +109,16 @@
<li><a href="{{ meta.server_url }}/reaction" id="reactionLink">Reaction</a></li> <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 }}/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 }}/scenario" id="scenarioLink">Scenario</a></li>
<li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</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 }}/user" id="userLink">User</a></li>#}
<!-- <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>--> {# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#}
</ul> </ul>
</li> </li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right navbar-nav-framework navbar-right-framework"> <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"> <li class="dropdown">
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a> <a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a>
<ul role="menu" class="dropdown-menu"> <ul role="menu" class="dropdown-menu">
@ -120,31 +134,33 @@
</ul> </ul>
</li> </li>
{% if meta.user.username == 'anonymous' %} {% if meta.user.username == 'anonymous' %}
<li> <li>
<a href="#signup" id="loginButton" data-toggle="modal" data-target="#signupmodal" <a href="#signup" id="loginButton" data-toggle="modal" data-target="#signupmodal"
style="margin-right:10px">Login</a> style="margin-right:10px">Login</a>
</li> </li>
{% else %} {% else %}
<li class="dropdown"> <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"
<div id="username"> href="#">
{{ user.username }}<b class="caret"></b> <div id="username">
</div> {{ user.username }}<b class="caret"></b>
</a>
<ul role="menu" class="dropdown-menu">
<li>
<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">
{% csrf_token %}
<div class="form-group">
<input type="hidden" name="logout" value="true">
</div> </div>
<button type="submit" class="btn btn-default">Logout</button> </a>
</form> <ul role="menu" class="dropdown-menu">
</ul> <li>
</li> <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">
{% csrf_token %}
<div class="form-group">
<input type="hidden" name="logout" value="true">
</div>
<button type="submit" class="btn btn-default">Logout</button>
</form>
</ul>
</li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
@ -152,26 +168,26 @@
</nav> </nav>
<div id="docContent" class="content container"> <div id="docContent" class="content container">
{% if breadcrumbs %} {% if breadcrumbs %}
<div id="bread"> <div id="bread">
<ol class="breadcrumb"> <ol class="breadcrumb">
{% for elem in breadcrumbs %} {% for elem in breadcrumbs %}
{% for name, url in elem.items %} {% for name, url in elem.items %}
{% if forloop.parentloop.last %} {% if forloop.parentloop.last %}
<li class="active">{{ name }}</li> <li class="active">{{ name }}</li>
{% else %} {% else %}
<li> <li>
<a href="{{ url }}">{{ name }}</a> <a href="{{ url }}">{{ name }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %}
{% endfor %} {% endfor %}
{% endfor %} </ol>
</ol> </div>
</div>
{% endif %} {% endif %}
{% if message %} {% if message %}
<div id="message"> <div id="message">
{{ message }} {{ message }}
</div> </div>
{% endif %} {% endif %}
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}
@ -206,19 +222,26 @@
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<ul class="nav nav-pills nav-justified"> <ul class="nav nav-pills nav-justified">
<!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>--> <!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>-->
<li><a href="mailto:admin@envipath.org" target="_blank">Contact</a></li> <li><a href="mailto:admin@envipath.org" target="_blank">Contact</a></li>
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschr&auml;nkt) &amp; Co. KG &copy;--> <!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschr&auml;nkt) &amp; Co. KG &copy;-->
<!-- {{ YEAR }}</a></li>--> <!-- {{ YEAR }}</a></li>-->
</ul> </ul>
</div> </div>
</div> </div>
<script>
$(function () {
// Hide actionsbutton if theres no action defined
if ($('#actionsButton ul').children().length > 0) {
$('#actionsButton').show();
}
});
</script>
{% block modals %} {% block modals %}
{% include "modals/cite_modal.html" %} {% include "modals/cite_modal.html" %}
{% include "modals/signup_modal.html" %} {% include "modals/signup_modal.html" %}
{% include "modals/predict_modal.html" %} {% include "modals/predict_modal.html" %}
{% include "modals/batch_predict_modal.html" %} {% include "modals/batch_predict_modal.html" %}
{% endblock %} {% endblock %}
</body> </body>
</html> </html>

View File

@ -2,139 +2,166 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<!-- TODO rename ids as well as remove pathways if modal is closed!--> <!-- 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"
<div class="modal-dialog"> aria-hidden="true">
<div class="modal-content"> <div class="modal-dialog">
<div class="modal-header"> <div class="modal-content">
<button type="button" class="close" data-dismiss="modal"> <div class="modal-header">
<span aria-hidden="true">&times;</span> <button type="button" class="close" data-dismiss="modal">
<span class="sr-only">Close</span> <span aria-hidden="true">&times;</span>
</button> <span class="sr-only">Close</span>
<h4 class="modal-title" id="newPackMod">Found Pathway in Database</h4> </button>
</div> <h4 class="modal-title" id="newPackMod">Found Pathway in Database</h4>
<div class="modal-body"> </div>
<p>We found at least one pathway in the database with the given root <div class="modal-body">
compound. Do you want to open any of the existing pathways or <p>We found at least one pathway in the database with the given root
predict a new one? To open an existing pathway, simply click compound. Do you want to open any of the existing pathways or
on the pathway, to predict a new one, click Predict. The predicted predict a new one? To open an existing pathway, simply click
pathway might differ from the ones in the database due to the on the pathway, to predict a new one, click Predict. The predicted
settings used in the prediction.</p> pathway might differ from the ones in the database due to the
<div id="foundPathways"></div> settings used in the prediction.</p>
</div> <div id="foundPathways"></div>
<div class="modal-footer"> </div>
<a id="modal-predict" class="btn btn-primary" href="#">Predict</a> <div class="modal-footer">
<button type="button" id="cancel-predict" class="btn btn-default" data-dismiss="modal">Cancel</button> <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>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="jumbotron">
<div class="jumbotron"> <h1>
<h1> <img id="image-logo-long" class="img-responsive" alt="enviPath" width="1000ex"
<img id="image-logo-long" class="img-responsive" alt="enviPath" width="1000ex" src='{% static "/images/logo-long.svg" %}'/>
src='{% static "/images/logo-long.svg" %}'/> </h1>
</h1> <p>enviPath is a database and prediction system for the microbial
<p>enviPath is a database and prediction system for the microbial biotransformation of organic environmental contaminants. The
biotransformation of organic environmental contaminants. The database provides the possibility to store and view experimentally
database provides the possibility to store and view experimentally observed biotransformation pathways. The pathway prediction system
observed biotransformation pathways. The pathway prediction system provides different relative reasoning models to predict likely biotransformation
provides different relative reasoning models to predict likely biotransformation pathways and products. You can try it out below.
pathways and products. You can try it out below. </p>
</p> <p>
<p> <a class="btn" style="background-color:#222222;color:#9d9d9d" role="button" target="_blank"
<a class="btn" style="background-color:#222222;color:#9d9d9d" role="button" target="_blank" href="https://wiki.envipath.org/index.php/Main_Page">Learn more &gt;&gt;</a>
href="https://wiki.envipath.org/index.php/Main_Page">Learn more &gt;&gt;</a> </p>
</p>
</div>
<p></p>
<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">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<iframe id="index-form-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
height="510"></iframe>
</li>
</ul>
</div> </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"> <p></p>
<div class="input-group-btn"> <form id="index-form" action="{{ meta.current_package.url }}/pathway" method="POST">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" {% csrf_token %}
aria-expanded="false" id="action-button">Predict <span class="caret"></span></button> <div class="input-group" id="index-form-bar">
<ul class="dropdown-menu"> <div class="input-group-btn">
<li><a id="dropdown-predict">Predict</a></li> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
<li><a id="dropdown-search">Search</a></li> aria-expanded="false">
</ul> <span class="caret"></span>
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="run-button">Go!</button> </button>
</div> <ul class="dropdown-menu" role="menu">
</div> <li>
<iframe id="index-form-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
height="510"></iframe>
</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">
<div class="input-group-btn">
<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>
</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 }}";
<div id="loading"></div> function goButtonClicked() {
<script language="javascript"> $(this).prop("disabled", true);
function goButtonClicked() { var action = $('#action-button').text().trim();
$(this).prop("disabled", true);
var action = $('#action-button').text().trim(); var textSmiles = $('#index-form-text-input').val().trim();
var textSmiles = $('#index-form-text-input').val().trim(); if (textSmiles === '') {
var ketcherSmiles = getKetcher('index-form-ketcher').getSmiles().trim(); return;
}
if (action != 'Search' && ketcherSmiles != '' && textSmiles != ketcherSmiles) { var ketcherSmiles = getKetcher('index-form-ketcher').getSmiles().trim();
console.log("Ketcher and TextInput differ!");
if (action !== 'Search' && ketcherSmiles !== '' && textSmiles !== ketcherSmiles) {
console.log("Ketcher and TextInput differ!");
}
if (action === 'Search') {
var par = {};
par['search'] = textSmiles;
par['mode'] = 'text';
var queryString = $.param(par, true);
window.location.href = "/search?" + queryString;
} else {
$('#index-form-smiles').val(textSmiles);
$('#index-form').submit();
}
} }
if (action == 'Search') { function actionDropdownClicked() {
console.log("Searching..."); var suffix = ' <span class="caret"></span>';
} else { var dropdownVal = $(this).text();
console.log("Predicting");
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);
} }
} function ketcherToTextInput() {
$('#index-form-text-input').val(this.ketcher.getSmiles());
}
function actionDropdownClicked() { $(function () {
var suffix = ' <span class="caret"></span>'; // Code that should be executed once DOM is ready goes here
var dropdownVal = $(this).text(); $('#dropdown-predict').on('click', actionDropdownClicked);
$('#action-button').html(dropdownVal + suffix); $('#dropdown-search').on('click', actionDropdownClicked);
}
function ketcherToTextInput() { $('#run-button').on('click', goButtonClicked);
$('#index-form-text-input').val(this.ketcher.getSmiles());
}
$(function() { // Update Ketcher Width
// Code that should be executed once DOM is ready goes here var fullWidth = $('#index-form-bar').width();
$('#dropdown-predict').on('click', actionDropdownClicked); $('#index-form-ketcher').width(fullWidth);
$('#dropdown-search').on('click', actionDropdownClicked);
$('#run-button').on('click', goButtonClicked); // add a listener that gets triggered whenever the structure in ketcher has changed
$('#index-form-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: ketcherToTextInput,
ketcher: win.ketcher
});
} else {
setTimeout(checkKetcherReady, 100);
}
};
// Update Ketcher Width checkKetcherReady();
var fullWidth = $('#index-form-bar').width(); });
$('#index-form-ketcher').width(fullWidth);
// add a listener that gets triggered whenever the structure in ketcher has changed
$('#index-form-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: ketcherToTextInput,
ketcher: win.ketcher
});
} else {
setTimeout(checkKetcherReady, 100);
}
};
checkKetcherReady();
}); });
</script>
});
</script>
{% endblock content %} {% endblock content %}

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"/> <input id="rule-name" class="form-control" name="rule-name" placeholder="Name"/>
<label for="rule-description">Description</label> <label for="rule-description">Description</label>
<input id="rule-description" class="form-control" name="rule-description" placeholder="Description"/> <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> <p></p>
<!-- TODO Ruletypes --> <div id="rule-smirks-viz"></div>
<!-- TODO Decide on rules to use?-->
<input type="hidden" name="rule-type" id="rule-type" value="SimpleAmbitRule"> <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> <p></p>
</form> </form>
</div> </div>
@ -47,13 +35,36 @@
</div> </div>
<script> <script>
$(function() { $(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) { $('#new_rule_modal_form_submit').on('click', function(e) {
e.preventDefault(); e.preventDefault();
$(this).prop("disabled",true); $(this).prop("disabled",true);
k = getKetcher('new_rule_ketcher');
$('#rule-smirks').val(k.getSmiles());
// submit form // submit form
$('#new_rule_modal_form').submit(); $('#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-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <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"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <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"> <form id="edit-compound-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %} {% csrf_token %}
<p> <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> <h3 class="modal-title">Update Reaction</h3>
</div> </div>
<div class="modal-body"> <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"> <form id="edit-reaction-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %} {% csrf_token %}
<p> <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(); e.preventDefault();
const formData = $('#request_api_token_form').serialize(); const formData = $('#request_api_token_form').serialize();
console.log(formData)
$.post('', formData, function(response) { $.post('', formData, function(response) {
$('#new-token-pre').text(response.raw_token); $('#new-token-pre').text(response.raw_token);
$('#new-token').show(); $('#new-token').show();

View File

@ -11,7 +11,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ rule.name }} {{ rule.name }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -2,135 +2,141 @@
{% block content %} {% block content %}
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_compound_modal.html" %} {% include "modals/objects/edit_compound_modal.html" %}
{% include "modals/objects/add_structure_modal.html" %} {% include "modals/objects/add_structure_modal.html" %}
{% include "modals/objects/delete_compound_modal.html" %} {% include "modals/objects/delete_compound_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="compound-detail"> <div class="panel-group" id="compound-detail">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ compound.name }} {{ compound.name }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
style="padding-right:1em"></span></a> style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu"> <ul id="actionsList" class="dropdown-menu">
{% block actions %} {% block actions %}
{% include "actions/objects/compound.html" %} {% include "actions/objects/compound.html" %}
{% endblock %} {% endblock %}
</ul> </ul>
</div>
</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>
</p>
</div>
<!-- Description -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound-desc-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-desc">Description</a>
</h4>
</div>
<div id="compound-desc" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ compound.description }}
</div>
</div>
<!-- Image -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound-image-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-image">Image Representation</a>
</h4>
</div>
<div id="compound-image" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<div id="image-div" align="center">
{{ compound.default_structure.as_svg|safe }}
</div> </div>
</div> </div>
</div> <div class="panel-body">
<p>
<!-- SMILES --> The structures stored in this compound can be found at <a target="_blank"
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> href="{{ compound.url }}/structure"
<h4 class="panel-title"> role="button">Compound structures
<a id="compound-smiles-link" data-toggle="collapse" data-parent="#compound-detail" &gt;&gt;</a>
href="#compound-smiles">SMILES Representation</a> </p>
</h4>
</div>
<div id="compound-smiles" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ compound.default_structure.smiles }}
</div> </div>
</div>
<!-- Canonical SMILES --> <!-- Description -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="compound-canonical-smiles-link" data-toggle="collapse" data-parent="#compound-detail" <a id="compound-desc-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-canonical-smiles">Canonical SMILES Representation</a> href="#compound-desc">Description</a>
</h4> </h4>
</div>
<div id="compound-canonical-smiles" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ compound.default_structure.canonical_smiles }}
</div> </div>
</div> <div id="compound-desc" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<!-- InChiKey --> {{ compound.description }}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> </div>
<h4 class="panel-title">
<a id="compound-inchi-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-inchi">InChIKey</a>
</h4>
</div>
<div id="compound-inchi" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ compound.default_structure.InChIKey }}
</div> </div>
</div>
<!-- Reactions --> <!-- Image -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="compound-reaction-link" data-toggle="collapse" data-parent="#compound-detail" <a id="compound-image-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-reaction">Reactions</a> href="#compound-image">Image Representation</a>
</h4> </h4>
</div>
<div id="compound-reaction" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for r in compound.related_reactions %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }} <i>({{ r.package.name }})</i></a>
{% endfor %}
</div> </div>
</div> <div id="compound-image" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<!-- Pathways --> <div id="image-div" align="center">
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> {{ compound.default_structure.as_svg|safe }}
<h4 class="panel-title"> </div>
<a id="compound-pathway-link" data-toggle="collapse" data-parent="#compound-detail" </div>
href="#compound-pathway">Pathways</a>
</h4>
</div>
<div id="compound-pathway" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for r in compound.related_pathways %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }} <i>({{ r.package.name }})</i></a>
{% endfor %}
</div> </div>
</div>
<!-- External Identifiers --> <!-- SMILES -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound-smiles-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-smiles">SMILES Representation</a>
</h4>
</div>
<div id="compound-smiles" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ compound.default_structure.smiles }}
</div>
</div>
<!-- Canonical SMILES -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound-canonical-smiles-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-canonical-smiles">Canonical SMILES Representation</a>
</h4>
</div>
<div id="compound-canonical-smiles" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ compound.default_structure.canonical_smiles }}
</div>
</div>
<!-- InChiKey -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound-inchi-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-inchi">InChIKey</a>
</h4>
</div>
<div id="compound-inchi" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ 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"
href="#compound-reaction">Reactions</a>
</h4>
</div>
<div id="compound-reaction" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for r in compound.related_reactions %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }} <i>({{ r.package.name }})</i></a>
{% 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"
href="#compound-pathway">Pathways</a>
</h4>
</div>
<div id="compound-pathway" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for r in compound.related_pathways %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }} <i>({{ r.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- External Identifiers -->
</div>
</div> </div>
</div>
{% endblock content %} {% endblock content %}

View File

@ -11,7 +11,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ compound_structure.name }} {{ compound_structure.name }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><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"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ group.name }} {{ group.name }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -1,263 +1,322 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% load envipytags %}
{% block content %} {% block content %}
{% block action_modals %} {% block action_modals %}
{% endblock action_modals %} {% include "modals/objects/delete_model_modal.html" %}
{% endblock action_modals %}
<!-- Include required libs --> <!-- Include required libs -->
<script src="https://d3js.org/d3.v5.min.js"></script> <script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.css" rel="stylesheet">
<div class="panel-group" id="model-detail"> <div class="panel-group" id="model-detail">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ model.name }} {{ model.name }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
style="padding-right:1em"></span></a> style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu"> <ul id="actionsList" class="dropdown-menu">
{% block actions %} {% block actions %}
{% endblock %} {% include "actions/objects/model.html" %}
</ul> {% endblock %}
</div> </ul>
</div>
<div class="panel-body">
<p> {{ model.description }} </p>
</div>
<!-- Predict Panel -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="predict-smiles-link" data-toggle="collapse" data-parent="#model-detail"
href="#predict-smiles">Predict</a>
</h4>
</div>
<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">
<span class="input-group-btn">
<button class="btn btn-default" type="submit" id="predict-button">Predict!</button>
</span>
</div> </div>
<div id="loading"></div>
<div id="predictResultTable"></div>
</div> </div>
</div> <div class="panel-body">
<!-- End Predict Panel --> <p> {{ model.description }} </p>
{% if model.model_status == 'FINISHED' %} </div>
<!-- Single Gen Curve Panel --> {% if model|classname == 'MLRelativeReasoning' %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <!-- Rule Packages -->
<h4 class="panel-title"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<a id="sg-curve-link" data-toggle="collapse" data-parent="#model-detail" <h4 class="panel-title">
href="#sg-curve">Predict</a> <a id="rule-package-link" data-toggle="collapse" data-parent="#model-detail"
</h4> href="#rule-package">Rule Packages</a>
</div> </h4>
<div id="sg-curve" class="panel-collapse collapse in"> </div>
<div class="panel-body list-group-item"> <div id="rule-package" class="panel-collapse collapse in">
<!-- Center container contents --> <div class="panel-body list-group-item">
<div class="container" style="display: flex;justify-content: center;"> {% for p in model.rule_packages.all %}
<div id="sg-curve-plotdiv" class="chart"> <a class="list-group-item" href="{{ p.url }}">{{ p.name }}</a>
<div id="sg-chart"></div> {% endfor %}
</div> </div>
</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">
<a id="predict-smiles-link" data-toggle="collapse" data-parent="#model-detail"
href="#predict-smiles">Predict</a>
</h4>
</div> </div>
</div> <div id="predict-smiles" class="panel-collapse collapse in">
<script> <div class="panel-body list-group-item">
$(function() { <div class="input-group">
if(!($('#sg-chart').length > 0)) { <input id="smiles-to-predict" type="text" class="form-control"
return; placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C">
} <span class="input-group-btn">
console.log($('#sg-chart').length) <button class="btn btn-default" type="submit" id="predict-button">Predict!</button>
//$('#sg-curve-plotdiv').empty(); </span>
</div>
<div id="loading"></div>
<div id="predictResultTable"></div>
</div>
</div>
<!-- End Predict Panel -->
{% if model.model_status == 'FINISHED' %}
<!-- Single Gen Curve Panel -->
<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">Precision Recall Curve</a>
</h4>
</div>
<div id="sg-curve" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<!-- Center container contents -->
<div class="container" style="display: flex;justify-content: center;">
<div id="sg-curve-plotdiv" class="chart">
<div id="sg-chart"></div>
</div>
</div>
var x = ['Recall']; </div>
var y = ['Precision']; </div>
var thres = ['threshold']; <script>
$(function () {
if (!($('#sg-chart').length > 0)) {
return;
}
// Compare function for the given array var x = ['Recall'];
function compare(a, b) { var y = ['Precision'];
if (a.threshold < b.threshold) var thres = ['threshold'];
// Compare function for the given array
function compare(a, b) {
if (a.threshold < b.threshold)
return -1;
else if (a.threshold > b.threshold)
return 1;
else
return 0;
}
function getIndexForValue(data, val, val_name) {
for (var idx in data) {
if (data[idx][val_name] == val) {
return idx;
}
}
return -1; return -1;
else if (a.threshold > b.threshold)
return 1;
else
return 0;
}
function getIndexForValue(data, val, val_name) {
for(var idx in data) {
if(data[idx][val_name] == val) {
return idx;
}
} }
return -1;
}
var data = {{ model.pr_curve|safe }} var data = {{ model.pr_curve|safe }}
var dataLength = Object.keys(data).length; var dataLength = Object.keys(data).length;
data.sort(compare); data.sort(compare);
for (var idx in data) { for (var idx in data) {
var d = data[idx]; var d = data[idx];
x.push(d.recall); x.push(d.recall);
y.push(d.precision); y.push(d.precision);
thres.push(d.threshold); thres.push(d.threshold);
} }
var chart = c3.generate({ var chart = c3.generate({
bindto: '#sg-chart', bindto: '#sg-chart',
data: { data: {
onclick: function (d, e) { onclick: function (d, e) {
var idx = d.index; var idx = d.index;
var thresh = data[dataLength-idx-1].threshold; var thresh = data[dataLength - idx - 1].threshold;
//onclick(thresh) //onclick(thresh)
},
x: 'Recall',
y: 'Precision',
columns: [
x,
y,
//thres
]
},
size: {
height: 400, // TODO: Make variable to current modal width
width: 480
},
axis: {
x: {
max: 1,
min: 0,
label: 'Recall',
padding: 0,
tick: {
fit: true,
values: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
}
},
y: {
max: 1,
min: 0,
label: 'Precision',
padding: 0,
tick: {
fit: true,
values: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
}
}
},
point: {
r: 4
},
tooltip: {
format: {
title: function (recall) {
idx = getIndexForValue(data, recall, "recall");
if(idx != -1) {
return "Threshold: " + data[idx].threshold;
}
return "";
}, },
value: function (precision, ratio, id) { x: 'Recall',
return undefined; y: 'Precision',
columns: [
x,
y,
//thres
]
},
size: {
height: 400, // TODO: Make variable to current modal width
width: 480
},
axis: {
x: {
max: 1,
min: 0,
label: 'Recall',
padding: 0,
tick: {
fit: true,
values: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
}
},
y: {
max: 1,
min: 0,
label: 'Precision',
padding: 0,
tick: {
fit: true,
values: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
}
} }
},
point: {
r: 4
},
tooltip: {
format: {
title: function (recall) {
idx = getIndexForValue(data, recall, "recall");
if (idx != -1) {
return "Threshold: " + data[idx].threshold;
}
return "";
},
value: function (precision, ratio, id) {
return undefined;
}
}
},
zoom: {
enabled: true
} }
}, });
zoom: {
enabled: true
}
}); });
}); </script>
</script> <!-- End Single Gen Curve Panel -->
<!-- End Single Gen Curve Panel --> {% endif %}
{% endif %} </div>
</div> </div>
</div>
<script> <script>
function handleResponse(data) { function handleResponse(data) {
res = "<table class='table table-striped'>" res = "<table class='table table-striped'>"
res += "<thead>" res += "<thead>"
res += "<th scope='col'>#</th>" res += "<th scope='col'>#</th>"
columns = ['products', 'image', 'probability', 'btrule'] columns = ['products', 'image', 'probability', 'btrule']
for(col in columns) { for (col in columns) {
res += "<th scope='col'>" + columns[col] + "</th>" res += "<th scope='col'>" + columns[col] + "</th>"
}
res += "</thead>"
res += "<tbody>"
var cnt = 1;
for(transformation in data) {
res += "<tr>"
res += "<th scope='row'>" + cnt + "</th>"
res += "<th scope='row'>" + data[transformation]['products'][0].join(', ') + "</th>"
res += "<th scope='row'>" + "<img width='400' src='{% url 'depict' %}?smiles=" + encodeURIComponent(data[transformation]['products'][0].join('.')) + "'></th>"
res += "<th scope='row'>" + data[transformation]['probability'].toFixed(3) + "</th>"
if (data[transformation]['btrule'] != null) {
res += "<th scope='row'>" + "<a href='" + data[transformation]['btrule']['url'] + "'>" + data[transformation]['btrule']['name'] + "</a>" + "</th>"
} else {
res += "<th scope='row'>N/A</th>"
}
res += "</tr>"
cnt += 1;
}
res += "</tbody>"
res += "</table>"
$("#predictResultTable").append(res);
}
function clear() {
$("#predictResultTable").removeClass("alert alert-danger");
$("#predictResultTable").empty();
}
if($('#predict-button').length > 0) {
$("#predict-button").on("click", function(e) {
e.preventDefault();
data = {
"smiles": $("#smiles-to-predict").val(),
"classify": "ILikeCats!"
} }
clear(); res += "</thead>"
res += "<tbody>"
var cnt = 1;
for (transformation in data) {
res += "<tr>"
res += "<th scope='row'>" + cnt + "</th>"
res += "<th scope='row'>" + data[transformation]['products'][0].join(', ') + "</th>"
res += "<th scope='row'>" + "<img width='400' src='{% url 'depict' %}?smiles=" + encodeURIComponent(data[transformation]['products'][0].join('.')) + "'></th>"
res += "<th scope='row'>" + data[transformation]['probability'].toFixed(3) + "</th>"
if (data[transformation]['btrule'] != null) {
res += "<th scope='row'>" + "<a href='" + data[transformation]['btrule']['url'] + "'>" + data[transformation]['btrule']['name'] + "</a>" + "</th>"
} else {
res += "<th scope='row'>N/A</th>"
}
res += "</tr>"
cnt += 1;
}
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}"); res += "</tbody>"
$.ajax({ res += "</table>"
type : 'get', $("#predictResultTable").append(res);
data : data, }
url : '',
success: function(data, textStatus) { function clear() {
try { $("#predictResultTable").removeClass("alert alert-danger");
$("#loading").empty(); $("#predictResultTable").empty();
handleResponse(data); }
} catch (error) {
console.log("Error"); if ($('#predict-button').length > 0) {
$("#predict-button").on("click", function (e) {
e.preventDefault();
data = {
"smiles": $("#smiles-to-predict").val(),
"classify": "ILikeCats!"
}
clear();
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}");
$.ajax({
type: 'get',
data: data,
url: '',
success: function (data, textStatus) {
try {
$("#loading").empty();
handleResponse(data);
} catch (error) {
console.log("Error");
$("#loading").empty();
$("#predictResultTable").addClass("alert alert-danger");
$("#predictResultTable").append("Error while processing request :/");
}
},
error: function (jqXHR, textStatus, errorThrown) {
$("#loading").empty(); $("#loading").empty();
$("#predictResultTable").addClass("alert alert-danger"); $("#predictResultTable").addClass("alert alert-danger");
$("#predictResultTable").append("Error while processing request :/"); $("#predictResultTable").append("Error while processing request :/");
} }
}, });
error : function(jqXHR, textStatus, errorThrown) {
$("#loading").empty();
$("#predictResultTable").addClass("alert alert-danger");
$("#predictResultTable").append("Error while processing request :/");
}
}); });
}); }
} </script>
</script>
{% endblock content %} {% endblock content %}

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"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ package.name }} {{ package.name }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -1,113 +1,182 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% block content %} {% block content %}
{% load static %} {% load static %}
<script src="https://d3js.org/d3.v7.min.js"></script> <script src="https://d3js.org/d3.v7.min.js"></script>
<style> <style>
svg { width: 100%; height: 600px; color: red;} svg {
.link { stroke: #999; stroke-opacity: 0.6; marker-end: url(#arrow); } width: 100%;
.link_no_arrow { stroke: #999; stroke-opacity: 0.6; } height: 600px;
.node image { cursor: pointer; } color: red;
.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> .link {
stroke: #999;
stroke-opacity: 0.6;
marker-end: url(#arrow);
}
<div class="panel-group" id="pwAccordion"> .link_no_arrow {
<div class="panel panel-default"> stroke: #999;
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px"> stroke-opacity: 0.6;
{{ pathway.name }} }
</div>
</div> .node image {
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> cursor: pointer;
<h4 class="panel-title"> }
<a id="vizLink" data-toggle="collapse" data-parent="#pwAccordion" href="#viz">
Graphical Representation .node circle {
</a> fill: lightblue;
</h4> stroke: steelblue;
</div> stroke-width: 1.5px;
<div id="viz" class="panel-collapse collapse in"> }
<nav role="navigation" class="navbar navbar-default" style="margin: 0;">
<div class="navbar-header"> .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>
{% 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">
{{ pathway.name }}
</div>
</div> </div>
<div id="editbarCollapse" class="collapse navbar-collapse "> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<ul class="nav navbar-nav"> <h4 class="panel-title">
<li class="dropdown requiresWritePerm"> <a id="vizLink" data-toggle="collapse" data-parent="#pwAccordion" href="#viz">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" Graphical Representation
aria-expanded="false"> </a>
<span class="glyphicon glyphicon-edit"></span> </h4>
Edit </div>
<span class="caret"></span></a> <div id="viz" class="panel-collapse collapse in">
<ul id="editingList" class="dropdown-menu"> <nav role="navigation" class="navbar navbar-default" style="margin: 0;">
<li> <div class="navbar-header">
<a role="button" data-toggle="modal" id="showCompoundNames"> </div>
<span class="glyphicon glyphicon-eye-open"></span> Show Compound Names</a> <div id="editbarCollapse" class="collapse navbar-collapse ">
</li> <ul class="nav navbar-nav">
</ul> <li class="dropdown requiresWritePerm">
</li> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
</ul> aria-haspopup="true"
aria-expanded="false">
<ul class="nav navbar-nav navbar-right"> <span class="glyphicon glyphicon-edit"></span>
<li> Edit
<a role="button" data-toggle="modal" onclick="goFullscreen('pwcontent')"> <span class="caret"></span></a>
<span class="glyphicon glyphicon-fullscreen"></span>Fullscreen <ul id="editingList" class="dropdown-menu">
</a> {% block actions %}
</li> {% include "actions/objects/pathway.html" %}
<li> {% endblock %}
<button type="button" class="btn btn-default navbar-btn" data-toggle="tooltip" id="status" </ul>
data-original-title="" title="" data-content="Pathway prediction complete."><span </li>
class="glyphicon glyphicon-ok"></span></button> </ul>
&nbsp;
</li> <ul class="nav navbar-nav navbar-right">
</ul> <li>
<a role="button" data-toggle="modal" onclick="goFullscreen('pwcontent')">
<span class="glyphicon glyphicon-fullscreen"></span>
Fullscreen
</a>
</li>
<li>
{% 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>
</div>
</nav>
<div id="vizdiv">
<svg width="2000" height="2000">
{% if debug %}
<rect width="100%" height="100%" fill="aliceblue"/>
{% endif %}
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6"
orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#999"/>
</marker>
</defs>
<g id="zoomable"></g>
</svg>
<div id="tooltip" class="tooltip"></div>
</div>
</div>
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="DescriptionLink" data-toggle="collapse" data-parent="#pathwayAccordion"
href="#Description">Description</a></h4>
</div>
<div id="Description" class="panel-collapse collapse in">
<div class="panel-body list-group-item" id="DescriptionContent">
{{ pathway.description | safe }}
</div>
</div> </div>
</nav>
<div id="vizdiv">
<svg width="2000" height="2000">
{% if debug %}
<rect width="100%" height="100%" fill="aliceblue"/>
{% endif %}
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6"
orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#999"/>
</marker>
</defs>
<g id="zoomable"></g>
</svg>
<div id="tooltip" class="tooltip"></div>
</div> </div>
</div> </div>
<script>
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> function goFullscreen(id) {
<h4 class="panel-title"> var element = document.getElementById(id);
<a id="DescriptionLink" data-toggle="collapse" data-parent="#pathwayAccordion" if (element.mozRequestFullScreen) {
href="#Description">Description</a></h4> element.mozRequestFullScreen();
</div> } else if (element.webkitRequestFullScreen) {
<div id="Description" class="panel-collapse collapse in"> element.webkitRequestFullScreen();
<div class="panel-body list-group-item" id="DescriptionContent"> }
{{ pathway.description | safe }} }
</div>
</div>
</div>
<script>
function transformReferences(text) { function transformReferences(text) {
return text.replace(/\[\s*(http[^\]|]+)\s*\|\s*([^\]]+)\s*\]/g, '<a target="parent" href="$1">$2</a>'); return text.replace(/\[\s*(http[^\]|]+)\s*\|\s*([^\]]+)\s*\]/g, '<a target="parent" href="$1">$2</a>');
} }
pathway = {{ pathway.d3_json | safe }}; pathway = {{ pathway.d3_json | safe }};
$(function() { $(function () {
draw(pathway, 'vizdiv'); draw(pathway, 'vizdiv');
// TODO fix somewhere else... // TODO fix somewhere else...
var newDesc = transformReferences($('#DescriptionContent')[0].innerText); var newDesc = transformReferences($('#DescriptionContent')[0].innerText);
$('#DescriptionContent').html(newDesc); $('#DescriptionContent').html(newDesc);
}); });
</script> </script>
{% endblock content %} {% endblock content %}

View File

@ -2,104 +2,125 @@
{% block content %} {% block content %}
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_reaction_modal.html" %} {% include "modals/objects/edit_reaction_modal.html" %}
{% endblock action_modals %} {% include "modals/objects/delete_reaction_modal.html" %}
{% endblock action_modals %}
<div class="panel-group" id="reaction-detail"> <div class="panel-group" id="reaction-detail">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ reaction.name }} {{ reaction.name }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
style="padding-right:1em"></span></a> style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu"> <ul id="actionsList" class="dropdown-menu">
{% block actions %} {% block actions %}
{% include "actions/objects/reaction.html" %} {% include "actions/objects/reaction.html" %}
{% endblock %} {% endblock %}
</ul> </ul>
</div>
</div>
<!-- Description -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="reaction-desc-link" data-toggle="collapse" data-parent="#reaction-detail"
href="#reaction-desc">Description</a>
</h4>
</div>
<div id="reaction-desc" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ reaction.description }}
</div>
</div>
<!-- Image -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="reaction-image-link" data-toggle="collapse" data-parent="#reaction-detail"
href="#reaction-image">Image Representation</a>
</h4>
</div>
<div id="reaction-image" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<div id="image-div" align="center">
{{ reaction.as_svg|safe }}
</div> </div>
</div> </div>
</div>
<!-- Reaction Description --> <!-- Description -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="reaction-description-link" data-toggle="collapse" data-parent="#reaction-description-detail" <a id="reaction-desc-link" data-toggle="collapse" data-parent="#reaction-detail"
href="#reaction-description-smiles">Reaction Description</a> href="#reaction-desc">Description</a>
</h4> </h4>
</div> </div>
<div id="reaction-description-smiles" class="panel-collapse collapse in"> <div id="reaction-desc" class="panel-collapse collapse in">
<div class="panel-body list-group-item"> <div class="panel-body list-group-item">
{% for educt in reaction.educts.all %} {{ reaction.description }}
<a class="btn btn-default" href="{{ educt.url }}">{{ educt.name }}</a> </div>
{% endfor %}
<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 %}
</div> </div>
</div>
<!-- SMIRKS --> <!-- Image -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="reaction-smirks-link" data-toggle="collapse" data-parent="#reaction-detail" <a id="reaction-image-link" data-toggle="collapse" data-parent="#reaction-detail"
href="#reaction-smirks">SMIRKS Representation</a> href="#reaction-image">Image Representation</a>
</h4> </h4>
</div> </div>
<div id="reaction-smirks" class="panel-collapse collapse in"> <div id="reaction-image" class="panel-collapse collapse in">
<div class="panel-body list-group-item"> <div class="panel-body list-group-item">
{{ reaction.smirks }} <div id="image-div" align="center">
{{ reaction.as_svg|safe }}
</div>
</div>
</div> </div>
</div>
<!-- Pathways --> <!-- Reaction Description -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="reaction-pathway-link" data-toggle="collapse" data-parent="#reaction-detail" <a id="reaction-description-link" data-toggle="collapse" data-parent="#reaction-description-detail"
href="#reaction-pathway">Pathways</a> href="#reaction-description-smiles">Reaction Description</a>
</h4> </h4>
</div>
<div id="reaction-pathway" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for r in reaction.related_pathways %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
{% endfor %}
</div> </div>
</div> <div id="reaction-description-smiles" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% 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>
{% for product in reaction.products.all %}
<a class="btn btn-default" href="{{ product.url }}">{{ product.name }}</a>
{% endfor %}
</div>
</div>
<!-- SMIRKS -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="reaction-smirks-link" data-toggle="collapse" data-parent="#reaction-detail"
href="#reaction-smirks">SMIRKS Representation</a>
</h4>
</div>
<div id="reaction-smirks" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ reaction.smirks }}
</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">
<a id="reaction-pathway-link" data-toggle="collapse" data-parent="#reaction-detail"
href="#reaction-pathway">Pathways</a>
</h4>
</div>
<div id="reaction-pathway" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for r in reaction.related_pathways %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div> </div>
</div>
{% endblock content %} {% endblock content %}

View File

@ -10,7 +10,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ scenario.name }} {{ scenario.name }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -2,173 +2,184 @@
{% block content %} {% block content %}
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_rule_modal.html" %} {% include "modals/objects/edit_rule_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="rule-detail"> <div class="panel-group" id="rule-detail">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ rule.name }} {{ rule.name }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
style="padding-right:1em"></span></a> style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu"> <ul id="actionsList" class="dropdown-menu">
{% block actions %} {% block actions %}
{% include "actions/objects/rule.html" %} {% include "actions/objects/rule.html" %}
{% endblock %} {% endblock %}
</ul> </ul>
</div>
</div>
<div class="panel-body">
<p>
{{ rule.description }}
</p>
</div>
<!-- Representation -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-image-link" data-toggle="collapse" data-parent="#rule-detail"
href="#rule-image">Image Representation</a>
</h4>
</div>
<div id="rule-image" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<div id="image-div" align="center">
{{ rule.as_svg|safe }}
</div> </div>
</div> </div>
</div> <div class="panel-body">
<p>
{{ rule.description }}
</p>
</div>
<!-- SMIRKS --> <!-- Representation -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="rule-smirks-link" data-toggle="collapse" data-parent="#rule-detail" <a id="rule-image-link" data-toggle="collapse" data-parent="#rule-detail"
href="#rule-smirks">SMIRKS Representation</a> href="#rule-image">Image Representation</a>
</h4> </h4>
</div> </div>
<div id="rule-smirks" class="panel-collapse collapse in"> <div id="rule-image" class="panel-collapse collapse in">
<div class="panel-body list-group-item"> <div class="panel-body list-group-item">
<p> {{ rule.smirks }} </p> <div id="image-div" align="center">
{{ rule.as_svg|safe }}
</div>
</div>
</div> </div>
</div>
<!-- Reactants SMARTS --> <!-- SMIRKS -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="rule-reactants-smarts-link" data-toggle="collapse" data-parent="#rule-detail" <a id="rule-smirks-link" data-toggle="collapse" data-parent="#rule-detail"
href="#rule-reactants-smarts">Reactant SMARTS</a> href="#rule-smirks">SMIRKS Representation</a>
</h4> </h4>
</div> </div>
<div id="rule-reactants-smarts" class="panel-collapse collapse in"> <div id="rule-smirks" class="panel-collapse collapse in">
<div class="panel-body list-group-item"> <div class="panel-body list-group-item">
<p> {{ rule.reactants_smarts }} </p> <p> {{ rule.smirks }} </p>
</div>
</div> </div>
</div>
<!-- Reactant Filter SMARTS --> <!-- Reactants SMARTS -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="rule-reactant-filter-smarts-link" data-toggle="collapse" data-parent="#rule-detail" <a id="rule-reactants-smarts-link" data-toggle="collapse" data-parent="#rule-detail"
href="#rule-reactant-filter-smarts">Reactant Filter SMARTS</a> href="#rule-reactants-smarts">Reactant SMARTS</a>
</h4> </h4>
</div> </div>
<div id="rule-reactant-filter-smarts" class="panel-collapse collapse in"> <div id="rule-reactants-smarts" class="panel-collapse collapse in">
<div class="panel-body list-group-item"> <div class="panel-body list-group-item">
<p> {{ rule.reactant_filter_smarts }} </p> <p> {{ rule.reactants_smarts }} </p>
</div>
</div> </div>
</div>
<!-- Products SMARTS --> <!-- Reactant Filter SMARTS -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> {% if rule.reactant_filter_smarts %}
<h4 class="panel-title"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<a id="rule-products-smarts-link" data-toggle="collapse" data-parent="#rule-detail" <h4 class="panel-title">
href="#rule-products-smarts">Reactant SMARTS</a> <a id="rule-reactant-filter-smarts-link" data-toggle="collapse" data-parent="#rule-detail"
</h4> href="#rule-reactant-filter-smarts">Reactant Filter SMARTS</a>
</div> </h4>
<div id="rule-products-smarts" class="panel-collapse collapse in"> </div>
<div class="panel-body list-group-item"> <div id="rule-reactant-filter-smarts" class="panel-collapse collapse in">
<p> {{ rule.products_smarts }} </p> <div class="panel-body list-group-item">
</div> <p> {{ rule.reactant_filter_smarts }} </p>
</div> </div>
</div>
{% endif %}
<!-- Product Filter SMARTS --> <!-- Products SMARTS -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="rule-product-filter-smarts-link" data-toggle="collapse" data-parent="#rule-detail" <a id="rule-products-smarts-link" data-toggle="collapse" data-parent="#rule-detail"
href="#rule-product-filter-smarts">Product Filter SMARTS</a> href="#rule-products-smarts">Reactant SMARTS</a>
</h4> </h4>
</div> </div>
<div id="rule-product-filter-smarts" class="panel-collapse collapse in"> <div id="rule-products-smarts" class="panel-collapse collapse in">
<div class="panel-body list-group-item"> <div class="panel-body list-group-item">
<p> {{ rule.product_filter_smarts }} </p> <p> {{ rule.products_smarts }} </p>
</div>
</div> </div>
</div>
<!-- Included in Composite Rules --> <!-- Product Filter SMARTS -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> {% if rule.product_filter_smarts %}
<h4 class="panel-title"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<a id="rule-composite-rule-link" data-toggle="collapse" data-parent="#rule-detail" <h4 class="panel-title">
href="#rule-composite-rule">Included in Composite Rules</a> <a id="rule-product-filter-smarts-link" data-toggle="collapse" data-parent="#rule-detail"
</h4> href="#rule-product-filter-smarts">Product Filter SMARTS</a>
</div> </h4>
<div id="rule-composite-rule" class="panel-collapse collapse in"> </div>
<div class="panel-body list-group-item"> <div id="rule-product-filter-smarts" class="panel-collapse collapse in">
{% for cr in rule.parallelrule_set.all %} <div class="panel-body list-group-item">
<a class="list-group-item" href="{{ cr.url }}">{{ cr.name }}</a> <p> {{ rule.product_filter_smarts }} </p>
{% endfor %} </div>
</div> </div>
</div> {% endif %}
<!-- Scenarios -->
<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"
href="#rule-scenario">Scenarios</a>
</h4>
</div>
<div id="rule-scenario" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for s in rule.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}">{{ s.name }}</a>
{% endfor %}
</div>
</div>
<!-- Reactions --> <!-- Included in Composite Rules -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> {% if rule.parallelrule_set.all %}
<h4 class="panel-title"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<a id="rule-reaction-link" data-toggle="collapse" data-parent="#rule-detail" <h4 class="panel-title">
href="#rule-reaction">Reactions</a> <a id="rule-composite-rule-link" data-toggle="collapse" data-parent="#rule-detail"
</h4> href="#rule-composite-rule">Included in Composite Rules</a>
</div> </h4>
<div id="rule-reaction" class="panel-collapse collapse"> </div>
<div class="panel-body list-group-item"> <div id="rule-composite-rule" class="panel-collapse collapse in">
{% for r in rule.related_reactions %} <div class="panel-body list-group-item">
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a> {% for cr in rule.parallelrule_set.all %}
{% endfor %} <a class="list-group-item" href="{{ cr.url }}">{{ cr.name }}</a>
</div> {% endfor %}
</div> </div>
</div>
<!-- Pathways --> {% endif %}
<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"
href="#rule-pathway">Pathways</a>
</h4>
</div>
<div id="rule-pathway" class="panel-collapse collapse">
<div class="panel-body list-group-item">
{% for r in rule.related_pathways %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
{% endfor %}
</div>
</div>
<!-- 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"
href="#rule-scenario">Scenarios</a>
</h4>
</div>
<div id="rule-scenario" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for s in rule.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}">{{ s.name }}</a>
{% 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"
href="#rule-reaction">Reactions</a>
</h4>
</div>
<div id="rule-reaction" class="panel-collapse collapse">
<div class="panel-body list-group-item">
{% for r in rule.related_reactions %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
{% 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"
href="#rule-pathway">Pathways</a>
</h4>
</div>
<div id="rule-pathway" class="panel-collapse collapse">
<div class="panel-body list-group-item">
{% for r in rule.related_pathways %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div> </div>
</div>
{% endblock content %} {% endblock content %}

View File

@ -15,7 +15,7 @@
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px"> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ user.username }} {{ user.username }}
<div id="actionsButton" <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" class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span

View File

@ -1,62 +1,160 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<!--<script>-->
<!--$(document).arrive(".selPackages", function() {-->
<!-- // selectpicker triggers 'bootstrap-select' library-->
<!-- $(this).selectpicker();-->
<!--});-->
<!--</script>-->
<div id=searchContent> <div id=searchContent>
<div id="packSelector"> <div id="packSelector">
<label>Select Packages</label><br> <label>Select Packages</label><br>
<select id="selPackages" name="selPackages" data-actions-box='true' class="selPackages" multiple <select id="selPackages" name="selPackages" data-actions-box='true' class="selPackages" multiple
data-width='100%'> data-width='100%'>
{% if unreviewed_objects %} {% if unreviewed_objects %}
<option disabled>Reviewed Packages</option> <option disabled>Reviewed Packages</option>
{% endif %} {% endif %}
{% for obj in reviewed_objects %} {% for obj in reviewed_objects %}
<option value="{{ obj.url }}" selected>{{ obj.name }}</option> <option value="{{ obj.url }}" selected>{{ obj.name }}</option>
{% endfor %} {% endfor %}
{% if unreviewed_objects %} {% if unreviewed_objects %}
<option disabled>Unreviewed Packages</option> <option disabled>Unreviewed Packages</option>
{% endif %} {% endif %}
{% for obj in unreviewed_objects %} {% for obj in unreviewed_objects %}
<option value="{{ obj.url }}">{{ obj.name }}</option> <option value="{{ obj.url }}">{{ obj.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<p></p> <p></p>
<div> <div>
<label>Search Term</label><br> <label>Search Term</label><br>
<div class="input-group" id="index-form-bar"> <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"> <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"
aria-haspopup="true" aria-expanded="false">Text <span class="caret"></span></button> id="mode-button"
<ul class="dropdown-menu"> aria-haspopup="true" aria-expanded="false">Text <span class="caret"></span></button>
<li class="dropdown-header">Text</li> <ul class="dropdown-menu">
<li><a id="dropdown-predict-text-text">Text</a></li> <li class="dropdown-header">Text</li>
<li class="dropdown-header">SMILES</li> <li><a id="dropdown-predict-text-text">Text</a></li>
<li><a id="dropdown-search-smiles-default" data-toggle="tooltip">Default</a></li> <li class="dropdown-header">SMILES</li>
<li><a id="dropdown-search-smiles-canonical">Canonical</a></li> <li><a id="dropdown-search-smiles-default" data-toggle="tooltip">Default</a></li>
<li><a id="dropdown-search-smiles-exact">Exact</a></li> <li><a id="dropdown-search-smiles-canonical">Canonical</a></li>
<li class="dropdown-header">InChI</li> <li><a id="dropdown-search-smiles-exact">Exact</a></li>
<li><a id="dropdown-search-inchi-inchikey">InChIKey</a></li> <li class="dropdown-header">InChI</li>
</ul> <li><a id="dropdown-search-inchi-inchikey">InChIKey</a></li>
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="run-button">Go! </ul>
</button> <button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="search-button">
Go!
</button>
</div>
</div> </div>
<p></p>
<div id="results"></div>
<p></p>
<div id="loading"></div>
</div> </div>
<div id="results">
</div>
<div id="loading"></div>
</div> </div>
<script> <script>
function modeDropdownClicked() {
var suffix = ' <span class="caret"></span>';
var dropdownVal = $(this).text();
$('#mode-button').html(dropdownVal + suffix);
}
$(function() { 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 () {
tooltips = { tooltips = {
'dropdown-predict-text-text': 'The inserted pattern will be searched on all enviPath object names and descriptions', 'dropdown-predict-text-text': 'The inserted pattern will be searched on all enviPath object names and descriptions',
@ -71,44 +169,17 @@
placement: "top", placement: "top",
title: tooltips[key] title: tooltips[key]
}); });
$('#' + key).on('click', modeDropdownClicked);
}); });
$("#selPackages").selectpicker(); $("#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> </script>
{% endblock content %} {% endblock content %}

View File

@ -5,7 +5,7 @@ from epdb.models import Compound, User, CompoundStructure
class CompoundTest(TestCase): class CompoundTest(TestCase):
fixtures = ["test_fixture.json.gz"] fixtures = ["test_fixture.cleaned.json"]
def setUp(self): def setUp(self):
pass pass
@ -53,6 +53,14 @@ class CompoundTest(TestCase):
description='No Desc' description='No Desc'
) )
with self.assertRaises(ValueError):
_ = Compound.create(
self.package,
smiles=' ',
name='Afoxolaner',
description='No Desc'
)
def test_smiles_are_trimmed(self): def test_smiles_are_trimmed(self):
c = Compound.create( c = Compound.create(
self.package, 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) # return list(res)
@staticmethod
def is_valid_smirks(smirks: str) -> bool:
try:
rdChemReactions.ReactionFromSmarts(smirks)
return True
except:
return False
@staticmethod @staticmethod
def apply(smiles: str, smirks: str, preprocess_smiles: bool = True, bracketize: bool = False, def apply(smiles: str, smirks: str, preprocess_smiles: bool = True, bracketize: bool = False,
standardize: bool = True, kekulize: bool = True) -> List['ProductSet']: standardize: bool = True, kekulize: bool = True) -> List['ProductSet']: