Compare commits
52 Commits
main
...
beta_2025-
| Author | SHA1 | Date | |
|---|---|---|---|
| a16035677c | |||
| 498a53ab3d | |||
| aa3b53e94b | |||
| 3c8f0e80cb | |||
| 4158bd36cb | |||
| 4e02910c62 | |||
| 2babe7f7e2 | |||
| 7da3880a9b | |||
| 52931526c1 | |||
| dd7b28046c | |||
| 8592cfae50 | |||
| ec2b941a85 | |||
| 02d84a9b29 | |||
| 00d9188c0c | |||
| 13816ecaf3 | |||
| 6a4c8d96c3 | |||
| 97d0527565 | |||
| b45c99f7d3 | |||
| b95ec98a2f | |||
| c79a1f2040 | |||
| 6e6b394289 | |||
| ec387cc12e | |||
| a7637d046a | |||
| fc8192fb0d | |||
| c3c1d4f5cf | |||
| 3308d47071 | |||
| 1267ca8ace | |||
| ec52b8872d | |||
| 579cd519d0 | |||
| 280ddc7205 | |||
| c9d6d8b024 | |||
| a1aebfa54d | |||
| 79b4b1586c | |||
| aec61151ce | |||
| 026189ccd9 | |||
| 2c9f9038f3 | |||
| 2b8b6f286d | |||
| 2c4c9d95d9 | |||
| 43c95e3da7 | |||
| df896878f1 | |||
| 49e02ed97d | |||
| 4fff78541b | |||
| 9323a9f7d7 | |||
| 4e58a1fad7 | |||
| 9950112311 | |||
| 6eb1d1bd65 | |||
| bcd9451450 | |||
| 844d0708c9 | |||
| 7c3bc69b38 | |||
| 843e8e6f07 | |||
| acdb62c08f | |||
| ded50edaa2 |
19
.env-example
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# settings.py
|
||||||
|
EP_DATA_DIR=
|
||||||
|
ALLOWED_HOSTS=
|
||||||
|
DEBUG=
|
||||||
|
LOG_LEVEL=
|
||||||
|
ENVIFORMER_PRESENT=
|
||||||
|
FLAG_CELERY_PRESENT=
|
||||||
|
SERVER_URL=
|
||||||
|
PLUGINS_ENABLED=
|
||||||
|
# DB
|
||||||
|
POSTGRES_SERVICE_NAME=
|
||||||
|
POSTGRES_DB=
|
||||||
|
POSTGRES_USER=
|
||||||
|
POSTGRES_PASSWORD=
|
||||||
|
POSTGRES_PORT=
|
||||||
|
# MAIL
|
||||||
|
EMAIL_HOST_USER=
|
||||||
|
EMAIL_HOST_PASSWORD=
|
||||||
|
|
||||||
179
.gitignore
vendored
@ -1,176 +1,7 @@
|
|||||||
# ---> Python
|
*.pyc
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
.idea/
|
||||||
|
static/admin/
|
||||||
# Flask stuff:
|
static/django_extensions/
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# UV
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
#uv.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
|
|
||||||
# pdm
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
||||||
#pdm.lock
|
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
||||||
# in version control.
|
|
||||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
||||||
.pdm.toml
|
|
||||||
.pdm-python
|
|
||||||
.pdm-build/
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
.env
|
||||||
.venv
|
debug.log
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
|
||||||
|
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
||||||
#.idea/
|
|
||||||
|
|
||||||
# Ruff stuff:
|
|
||||||
.ruff_cache/
|
|
||||||
|
|
||||||
# PyPI configuration file
|
|
||||||
.pypirc
|
|
||||||
|
|
||||||
1
.python-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.10
|
||||||
5
envipath/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# This will make sure the app is always imported when
|
||||||
|
# Django starts so that shared_task will use this app.
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ('celery_app',)
|
||||||
14
envipath/api.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from epdb.api import router as epdb_app_router
|
||||||
|
from epdb.legacy_api import router as epdb_legacy_app_router
|
||||||
|
from ninja import NinjaAPI
|
||||||
|
|
||||||
|
api = NinjaAPI()
|
||||||
|
|
||||||
|
from ninja import NinjaAPI
|
||||||
|
|
||||||
|
api_v1 = NinjaAPI(title="API V1 Docs", urls_namespace="api-v1")
|
||||||
|
api_legacy = NinjaAPI(title="Legacy API Docs", urls_namespace="api-legacy")
|
||||||
|
|
||||||
|
# Add routers
|
||||||
|
api_v1.add_router("/", epdb_app_router)
|
||||||
|
api_legacy.add_router("/", epdb_legacy_app_router)
|
||||||
16
envipath/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for envipath project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
27
envipath/celery.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from celery import Celery
|
||||||
|
from celery.signals import setup_logging
|
||||||
|
|
||||||
|
# Set the default Django settings module for the 'celery' program.
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
|
||||||
|
|
||||||
|
app = Celery('envipath')
|
||||||
|
|
||||||
|
# Using a string here means the worker doesn't have to serialize
|
||||||
|
# the configuration object to child processes.
|
||||||
|
# - namespace='CELERY' means all celery-related configuration keys
|
||||||
|
# should have a `CELERY_` prefix.
|
||||||
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
|
|
||||||
|
|
||||||
|
@setup_logging.connect
|
||||||
|
def config_loggers(*args, **kwargs):
|
||||||
|
from logging.config import dictConfig
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
dictConfig(settings.LOGGING)
|
||||||
|
|
||||||
|
|
||||||
|
# Load task modules from all registered Django apps.
|
||||||
|
app.autodiscover_tasks()
|
||||||
349
envipath/settings.py
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
"""
|
||||||
|
Django settings for envipath project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 4.2.17.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from envipy_plugins import Classifier, Property, Descriptor
|
||||||
|
from sklearn.ensemble import RandomForestClassifier
|
||||||
|
from sklearn.tree import DecisionTreeClassifier
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
load_dotenv(BASE_DIR / '.env', override=False)
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '7!VTW`aZqg/UBLsM.P=m)2]lWqg>{+:xUgG1"WO@bCyaHR2Up8XW&g<*3.F4l2gi9c.E3}dHyA0D`&z?u#U%^7HYbj],eP"g_MS|3BNMD[mI>s#<i/%2ngZ~Oy+/w&@]'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
|
||||||
|
ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split(',')
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
# 3rd party
|
||||||
|
'django_extensions',
|
||||||
|
'oauth2_provider',
|
||||||
|
# Custom
|
||||||
|
'epdb',
|
||||||
|
'migration',
|
||||||
|
]
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'oauth2_provider.middleware.OAuth2TokenMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
OAUTH2_PROVIDER = {
|
||||||
|
"PKCE_REQUIRED": False, # Accept PKCE requests but dont require them
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.environ.get('REGISTRATION_MANDATORY', False) == 'True':
|
||||||
|
MIDDLEWARE.append('epdb.middleware.login_required_middleware.LoginRequiredMiddleware')
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'envipath.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': (os.path.join(BASE_DIR, 'templates'),),
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'envipath.wsgi.application'
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
|
"USER": os.environ['POSTGRES_USER'],
|
||||||
|
"NAME": os.environ['POSTGRES_DB'],
|
||||||
|
"PASSWORD": os.environ['POSTGRES_PASSWORD'],
|
||||||
|
"HOST": os.environ['POSTGRES_SERVICE_NAME'],
|
||||||
|
"PORT": os.environ['POSTGRES_PORT']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# EMAIL
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
EMAIL_HOST = 'mail.gandi.net'
|
||||||
|
EMAIL_HOST_USER = os.environ['EMAIL_HOST_USER']
|
||||||
|
EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD']
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = "epdb.User"
|
||||||
|
ADMIN_APPROVAL_REQUIRED = os.environ.get('ADMIN_APPROVAL_REQUIRED', 'False') == 'True'
|
||||||
|
|
||||||
|
# # SESAME
|
||||||
|
# SESAME_MAX_AGE = 300
|
||||||
|
# # TODO set to "home"
|
||||||
|
# LOGIN_REDIRECT_URL = "/"
|
||||||
|
LOGIN_URL = '/login/'
|
||||||
|
|
||||||
|
SERVER_URL = os.environ.get('SERVER_URL', 'http://localhost:8000')
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = [SERVER_URL]
|
||||||
|
|
||||||
|
AMBIT_URL = 'http://localhost:9001'
|
||||||
|
DEFAULT_VALUES = {
|
||||||
|
'description': 'no description'
|
||||||
|
}
|
||||||
|
|
||||||
|
EP_DATA_DIR = os.environ['EP_DATA_DIR']
|
||||||
|
MODEL_DIR = os.path.join(EP_DATA_DIR, 'models')
|
||||||
|
if not os.path.exists(MODEL_DIR):
|
||||||
|
os.mkdir(MODEL_DIR)
|
||||||
|
|
||||||
|
STATIC_DIR = os.path.join(EP_DATA_DIR, 'static')
|
||||||
|
if not os.path.exists(STATIC_DIR):
|
||||||
|
os.mkdir(STATIC_DIR)
|
||||||
|
|
||||||
|
LOG_DIR = os.path.join(EP_DATA_DIR, 'log')
|
||||||
|
if not os.path.exists(LOG_DIR):
|
||||||
|
os.mkdir(LOG_DIR)
|
||||||
|
|
||||||
|
PLUGIN_DIR = os.path.join(EP_DATA_DIR, 'plugins')
|
||||||
|
if not os.path.exists(PLUGIN_DIR):
|
||||||
|
os.mkdir(PLUGIN_DIR)
|
||||||
|
|
||||||
|
# Set this as our static root dir
|
||||||
|
STATIC_ROOT = STATIC_DIR
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# Where the sources are stored...
|
||||||
|
STATICFILES_DIRS = (
|
||||||
|
BASE_DIR / 'static',
|
||||||
|
)
|
||||||
|
|
||||||
|
FIXTURE_DIRS = (
|
||||||
|
BASE_DIR / 'fixtures',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": True,
|
||||||
|
"formatters": {
|
||||||
|
"simple": {
|
||||||
|
'format': '[%(asctime)s] %(levelname)s %(module)s - %(message)s',
|
||||||
|
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"level": "INFO",
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"formatter": "simple",
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"level": "DEBUG", # Or higher
|
||||||
|
"class": "logging.FileHandler",
|
||||||
|
"filename": os.path.join(LOG_DIR, "debug.log"),
|
||||||
|
"formatter": "simple"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
# For everything under epdb/ loaded via getlogger(__name__)
|
||||||
|
"epdb": {
|
||||||
|
"handlers": ["file"], # "console",
|
||||||
|
"propagate": True,
|
||||||
|
"level": os.environ.get('LOG_LEVEL', 'INFO')
|
||||||
|
},
|
||||||
|
# For everything under envipath/ loaded via getlogger(__name__)
|
||||||
|
'envipath': {
|
||||||
|
'handlers': ['file', 'console'],
|
||||||
|
'propagate': True,
|
||||||
|
'level': os.environ.get('LOG_LEVEL', 'INFO')
|
||||||
|
},
|
||||||
|
# For everything under utilities/ loaded via getlogger(__name__)
|
||||||
|
'utilities': {
|
||||||
|
'handlers': ['file', 'console'],
|
||||||
|
'propagate': True,
|
||||||
|
'level': os.environ.get('LOG_LEVEL', 'INFO')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Flags
|
||||||
|
ENVIFORMER_PRESENT = os.environ.get('ENVIFORMER_PRESENT', 'False') == 'True'
|
||||||
|
if ENVIFORMER_PRESENT:
|
||||||
|
print("Loading enviFormer")
|
||||||
|
device = os.environ.get('ENVIFORMER_DEVICE', 'cpu')
|
||||||
|
from enviformer import load
|
||||||
|
ENVIFORMER_INSTANCE = load(device=device)
|
||||||
|
print("loaded")
|
||||||
|
|
||||||
|
|
||||||
|
# If celery is not present set always eager to true which will cause delayed tasks to block until finished
|
||||||
|
FLAG_CELERY_PRESENT = os.environ.get('FLAG_CELERY_PRESENT', 'False') == 'True'
|
||||||
|
if not FLAG_CELERY_PRESENT:
|
||||||
|
CELERY_TASK_ALWAYS_EAGER = True
|
||||||
|
|
||||||
|
# Celery Configuration Options
|
||||||
|
CELERY_TIMEZONE = "Europe/Berlin"
|
||||||
|
# Celery Configuration
|
||||||
|
CELERY_BROKER_URL = 'redis://localhost:6379/0' # Use Redis as message broker
|
||||||
|
CELERY_RESULT_BACKEND = 'redis://localhost:6379/1'
|
||||||
|
CELERY_ACCEPT_CONTENT = ['json']
|
||||||
|
CELERY_TASK_SERIALIZER = 'json'
|
||||||
|
|
||||||
|
MODEL_BUILDING_ENABLED = os.environ.get('MODEL_BUILDING_ENABLED', 'False') == 'True'
|
||||||
|
APPLICABILITY_DOMAIN_ENABLED = os.environ.get('APPLICABILITY_DOMAIN_ENABLED', 'False') == 'True'
|
||||||
|
DEFAULT_RF_MODEL_PARAMS = {
|
||||||
|
'base_clf': RandomForestClassifier(
|
||||||
|
n_estimators=100,
|
||||||
|
max_features='log2',
|
||||||
|
random_state=42,
|
||||||
|
criterion='entropy',
|
||||||
|
ccp_alpha=0.0,
|
||||||
|
max_depth=3,
|
||||||
|
min_samples_leaf=1
|
||||||
|
),
|
||||||
|
'num_chains': 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_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,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_MAX_NUMBER_OF_NODES = 30
|
||||||
|
DEFAULT_MAX_DEPTH = 5
|
||||||
|
DEFAULT_MODEL_THRESHOLD = 0.25
|
||||||
|
|
||||||
|
# Loading Plugins
|
||||||
|
PLUGINS_ENABLED = os.environ.get('PLUGINS_ENABLED', 'False') == 'True'
|
||||||
|
if PLUGINS_ENABLED:
|
||||||
|
from utilities.plugin import discover_plugins
|
||||||
|
CLASSIFIER_PLUGINS = discover_plugins(_cls=Classifier)
|
||||||
|
PROPERTY_PLUGINS = discover_plugins(_cls=Property)
|
||||||
|
DESCRIPTOR_PLUGINS = discover_plugins(_cls=Descriptor)
|
||||||
|
else:
|
||||||
|
CLASSIFIER_PLUGINS = {}
|
||||||
|
PROPERTY_PLUGINS = {}
|
||||||
|
DESCRIPTOR_PLUGINS = {}
|
||||||
|
|
||||||
|
SENTRY_ENABLED = os.environ.get('SENTRY_ENABLED', 'False') == 'True'
|
||||||
|
if SENTRY_ENABLED:
|
||||||
|
import sentry_sdk
|
||||||
|
|
||||||
|
def before_send(event, hint):
|
||||||
|
# Check if was a handled exception by one of our loggers
|
||||||
|
if event.get('logger'):
|
||||||
|
for log_path in LOGGING.get('loggers').keys():
|
||||||
|
if event['logger'].startswith(log_path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
sentry_sdk.init(
|
||||||
|
dsn=os.environ.get('SENTRY_DSN'),
|
||||||
|
# Add data like request headers and IP for users,
|
||||||
|
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
|
||||||
|
send_default_pii=True,
|
||||||
|
environment=os.environ.get('SENTRY_ENVIRONMENT', 'development'),
|
||||||
|
before_send=before_send,
|
||||||
|
)
|
||||||
|
|
||||||
|
# compile into digestible flags
|
||||||
|
FLAGS = {
|
||||||
|
'MODEL_BUILDING': MODEL_BUILDING_ENABLED,
|
||||||
|
'CELERY': FLAG_CELERY_PRESENT,
|
||||||
|
'PLUGINS': PLUGINS_ENABLED,
|
||||||
|
'SENTRY': SENTRY_ENABLED,
|
||||||
|
'ENVIFORMER': ENVIFORMER_PRESENT,
|
||||||
|
'APPLICABILITY_DOMAIN': APPLICABILITY_DOMAIN_ENABLED,
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGIN_EXEMPT_URLS = [
|
||||||
|
'/api/legacy/',
|
||||||
|
'/o/token/',
|
||||||
|
'/o/userinfo/',
|
||||||
|
]
|
||||||
29
envipath/urls.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for envipath project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
from .api import api_v1, api_legacy
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", include("epdb.urls")),
|
||||||
|
path("", include("migration.urls")),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("api/v1/", api_v1.urls),
|
||||||
|
path("api/legacy/", api_legacy.urls),
|
||||||
|
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
||||||
|
]
|
||||||
16
envipath/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for envipath project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
0
epdb/__init__.py
Normal file
105
epdb/admin.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
User,
|
||||||
|
UserPackagePermission,
|
||||||
|
Group,
|
||||||
|
GroupPackagePermission,
|
||||||
|
Package,
|
||||||
|
MLRelativeReasoning,
|
||||||
|
Compound,
|
||||||
|
CompoundStructure,
|
||||||
|
SimpleAmbitRule,
|
||||||
|
ParallelRule,
|
||||||
|
Reaction,
|
||||||
|
Pathway,
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
Scenario,
|
||||||
|
Setting
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UserPackagePermissionAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GroupAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GroupPackagePermissionAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EPAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['name', 'description']
|
||||||
|
|
||||||
|
|
||||||
|
class PackageAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MLRelativeReasoningAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundStructureAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleAmbitRuleAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ParallelRuleAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ReactionAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PathwayAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NodeAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ScenarioAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SettingAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(User, UserAdmin)
|
||||||
|
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
|
||||||
|
admin.site.register(Group, GroupAdmin)
|
||||||
|
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
|
||||||
|
admin.site.register(Package, PackageAdmin)
|
||||||
|
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
||||||
|
admin.site.register(Compound, CompoundAdmin)
|
||||||
|
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
||||||
|
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
||||||
|
admin.site.register(ParallelRule, ParallelRuleAdmin)
|
||||||
|
admin.site.register(Reaction, ReactionAdmin)
|
||||||
|
admin.site.register(Pathway, PathwayAdmin)
|
||||||
|
admin.site.register(Node, NodeAdmin)
|
||||||
|
admin.site.register(Edge, EdgeAdmin)
|
||||||
|
admin.site.register(Setting, SettingAdmin)
|
||||||
|
admin.site.register(Scenario, ScenarioAdmin)
|
||||||
108
epdb/api.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from ninja import Router, Schema, Field
|
||||||
|
from ninja.errors import HttpError
|
||||||
|
from ninja.pagination import paginate
|
||||||
|
from ninja.security import HttpBearer
|
||||||
|
|
||||||
|
from .logic import PackageManager
|
||||||
|
from .models import User, Compound, APIToken
|
||||||
|
|
||||||
|
|
||||||
|
class BearerTokenAuth(HttpBearer):
|
||||||
|
def authenticate(self, request, token):
|
||||||
|
for token_obj in APIToken.objects.select_related("user").all():
|
||||||
|
if token_obj.check_token(token) and token_obj.is_valid():
|
||||||
|
return token_obj.user
|
||||||
|
raise HttpError(401, "Invalid or expired token")
|
||||||
|
|
||||||
|
|
||||||
|
def _anonymous_or_real(request):
|
||||||
|
if request.user.is_authenticated and not request.user.is_anonymous:
|
||||||
|
return request.user
|
||||||
|
return get_user_model().objects.get(username='anonymous')
|
||||||
|
|
||||||
|
|
||||||
|
router = Router(auth=BearerTokenAuth())
|
||||||
|
|
||||||
|
|
||||||
|
class UserSchema(Schema):
|
||||||
|
email: str
|
||||||
|
username: str
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
|
||||||
|
|
||||||
|
class PackageIn(Schema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
class PackageOut(Schema):
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
name: str
|
||||||
|
reviewed: bool
|
||||||
|
compound_links: str = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_compound_links(obj):
|
||||||
|
return f"{obj.url}/compound"
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Schema):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundSchema(Schema):
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
smiles: str = Field(None, alias="default_structure.smiles")
|
||||||
|
reviewed: bool = Field(None, alias="package.reviewed")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user", response={200: List[UserSchema], 403: Error})
|
||||||
|
def get_users(request):
|
||||||
|
return User.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package", response={200: List[PackageOut], 403: Error})
|
||||||
|
def get_packages(request):
|
||||||
|
return PackageManager.get_all_readable_packages(request.user, include_reviewed=True)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package", response=PackageOut)
|
||||||
|
def create_package(request, package: PackageIn):
|
||||||
|
user = request.auth
|
||||||
|
name = package.name.strip()
|
||||||
|
description = package.description.strip()
|
||||||
|
|
||||||
|
p = PackageManager.create_package(user, name, description=description)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}", response={200: PackageOut, 403: Error})
|
||||||
|
def get_package(request, package_uuid):
|
||||||
|
try:
|
||||||
|
return PackageManager.get_package_by_id(request.auth, package_id=package_uuid)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {'message': f'Getting Package with id {package_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/compound", response={200: List[CompoundSchema], 403: Error})
|
||||||
|
@paginate
|
||||||
|
def get_compounds(request):
|
||||||
|
qs = Compound.objects.none()
|
||||||
|
for p in PackageManager.get_reviewed_packages():
|
||||||
|
qs |= Compound.objects.filter(package=p)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/compound", response={200: List[CompoundSchema], 403: Error})
|
||||||
|
@paginate
|
||||||
|
def get_package_compounds(request, package_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.auth, package_uuid)
|
||||||
|
return Compound.objects.filter(package=p)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Compounds for Package with id {package_uuid} failed due to insufficient rights!'}
|
||||||
9
epdb/apps.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class EPDBConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'epdb'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import epdb.signals # noqa: F401
|
||||||
5
epdb/forms.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class EmailLoginForm(forms.Form):
|
||||||
|
email = forms.EmailField()
|
||||||
739
epdb/legacy_api.py
Normal file
@ -0,0 +1,739 @@
|
|||||||
|
from typing import List, Dict, Optional, Any
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from ninja import Router, Schema, Field, Form
|
||||||
|
|
||||||
|
from utilities.chem import FormatConverter
|
||||||
|
from .logic import PackageManager
|
||||||
|
from .models import Compound, CompoundStructure, Package, User, UserPackagePermission, Rule, Reaction, Scenario, Pathway
|
||||||
|
|
||||||
|
|
||||||
|
def _anonymous_or_real(request):
|
||||||
|
if request.user.is_authenticated and not request.user.is_anonymous:
|
||||||
|
return request.user
|
||||||
|
return get_user_model().objects.get(username='anonymous')
|
||||||
|
|
||||||
|
|
||||||
|
# router = Router(auth=SessionAuth())
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Schema):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleObject(Schema):
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
reviewStatus: bool = Field(None, alias="package.reviewed")
|
||||||
|
|
||||||
|
|
||||||
|
################
|
||||||
|
# Login/Logout #
|
||||||
|
################
|
||||||
|
class SimpleUser(Schema):
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
identifier: str = 'user'
|
||||||
|
name: str = Field(None, alias='username')
|
||||||
|
email: str = Field(None, alias='email')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response={200: SimpleUser, 403: Error})
|
||||||
|
def login(request, loginusername: Form[str], loginpassword: Form[str], hiddenMethod: Form[str]):
|
||||||
|
from django.contrib.auth import authenticate
|
||||||
|
from django.contrib.auth import login
|
||||||
|
email = User.objects.get(username=loginusername).email
|
||||||
|
user = authenticate(username=email, password=loginpassword)
|
||||||
|
if user:
|
||||||
|
login(request, user)
|
||||||
|
return user
|
||||||
|
else:
|
||||||
|
return 403, {'message': 'Invalid username or password'}
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleGroup(Schema):
|
||||||
|
id: str
|
||||||
|
identifier: str = 'group'
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
###########
|
||||||
|
# Package #
|
||||||
|
###########
|
||||||
|
class SimplePackage(SimpleObject):
|
||||||
|
identifier: str = 'package'
|
||||||
|
reviewStatus: bool = Field(None, alias="reviewed")
|
||||||
|
|
||||||
|
|
||||||
|
class PackageSchema(Schema):
|
||||||
|
description: str = Field(None, alias="description")
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
links: List[Dict[str, List[str | int]]] = Field([], alias="links")
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
primaryGroup: Optional[SimpleGroup] = None
|
||||||
|
readers: List[Dict[str, str]] = Field([], alias="readers")
|
||||||
|
reviewComment: str = Field(None, alias="review_comment")
|
||||||
|
reviewStatus: str = Field(None, alias="review_status")
|
||||||
|
writers: List[Dict[str, str]] = Field([], alias="writers")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_links(obj: Package):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'Pathways': [
|
||||||
|
f'{obj.url}/pathway', obj.pathways.count()
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
'Rules': [
|
||||||
|
f'{obj.url}/rule', obj.rules.count()
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
'Compounds': [
|
||||||
|
f'{obj.url}/compound', obj.compounds.count()
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
'Reactions': [
|
||||||
|
f'{obj.url}/reaction', obj.reactions.count()
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
'Relative Reasoning': [
|
||||||
|
f'{obj.url}/relative-reasoning', obj.models.count()
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
'Scenarios': [
|
||||||
|
f'{obj.url}/scenario', obj.scenarios.count()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_readers(obj: Package):
|
||||||
|
users = User.objects.filter(
|
||||||
|
id__in=UserPackagePermission.objects.filter(
|
||||||
|
package=obj,
|
||||||
|
permission=UserPackagePermission.READ[0]
|
||||||
|
).values_list('user', flat=True)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
return [{u.id: u.name} for u in users]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_writers(obj: Package):
|
||||||
|
users = User.objects.filter(
|
||||||
|
id__in=UserPackagePermission.objects.filter(
|
||||||
|
package=obj,
|
||||||
|
permission=UserPackagePermission.WRITE[0]
|
||||||
|
).values_list('user', flat=True)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
return [{u.id: u.name} for u in users]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_review_comment(obj):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_review_status(obj):
|
||||||
|
return 'reviewed' if obj.reviewed else 'unreviewed'
|
||||||
|
|
||||||
|
|
||||||
|
class PackageWrapper(Schema):
|
||||||
|
package: List['PackageSchema']
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package", response={200: PackageWrapper, 403: Error})
|
||||||
|
def get_packages(request):
|
||||||
|
return {'package': PackageManager.get_all_readable_packages(request.user, include_reviewed=True)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}", response={200: PackageSchema, 403: Error})
|
||||||
|
def get_package(request, package_uuid):
|
||||||
|
try:
|
||||||
|
return PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Package with id {package_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
################################
|
||||||
|
# Compound / CompoundStructure #
|
||||||
|
################################
|
||||||
|
class SimpleCompound(SimpleObject):
|
||||||
|
identifier: str = 'compound'
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundPathwayScenario(Schema):
|
||||||
|
scenarioId: str
|
||||||
|
scenarioName: str
|
||||||
|
scenarioType: str
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundSchema(Schema):
|
||||||
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
|
description: str = Field(None, alias="description")
|
||||||
|
externalReferences: Dict[str, List[str]] = Field(None, alias="external_references")
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
halflifes: List[Dict[str, str]] = Field([], alias="halflifes")
|
||||||
|
identifier: str = 'compound'
|
||||||
|
imageSize: int = 600
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
pathwayScenarios: List[CompoundPathwayScenario] = Field([], alias="pathway_scenarios")
|
||||||
|
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
|
||||||
|
pubchemCompoundReferences: List[str] = Field([], alias="pubchem_compound_references")
|
||||||
|
reactions: List['SimpleReaction'] = Field([], alias="related_reactions")
|
||||||
|
reviewStatus: str = Field(False, alias="review_status")
|
||||||
|
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
|
||||||
|
structures: List['CompoundStructureSchema'] = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_review_status(obj: CompoundStructure):
|
||||||
|
return 'reviewed' if obj.package.reviewed else 'unreviewed'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_external_references(obj: Compound):
|
||||||
|
# TODO
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_structures(obj: Compound):
|
||||||
|
return CompoundStructure.objects.filter(compound=obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_halflifes(obj: Compound):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_pubchem_compound_references(obj: Compound):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_pathway_scenarios(obj: Compound):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'scenarioId': 'https://envipath.org/package/5882df9c-dae1-4d80-a40e-db4724271456/scenario/cd8350cd-4249-4111-ba9f-4e2209338501',
|
||||||
|
'scenarioName': 'Fritz, R. & Brauner, A. (1989) - (00004)',
|
||||||
|
'scenarioType': 'Soil'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundWrapper(Schema):
|
||||||
|
compound: List['SimpleCompound']
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleCompoundStructure(SimpleObject):
|
||||||
|
identifier: str = 'structure'
|
||||||
|
reviewStatus: bool = Field(None, alias="compound.package.reviewed")
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundStructureSchema(Schema):
|
||||||
|
InChI: str = Field(None, alias="inchi")
|
||||||
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
|
canonicalSmiles: str = Field(None, alias="canonical_smiles")
|
||||||
|
charge: int = Field(None, alias="charge")
|
||||||
|
description: str = Field(None, alias="description")
|
||||||
|
externalReferences: Dict[str, List[str]] = Field(None, alias="external_references")
|
||||||
|
formula: str = Field(None, alias="formula")
|
||||||
|
halflifes: List[Dict[str, str]] = Field([], alias="halflifes")
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
identifier: str = 'structure'
|
||||||
|
imageSize: int = 600
|
||||||
|
inchikey: str = Field(None, alias="inchikey")
|
||||||
|
isDefaultStructure: bool = Field(None, alias="is_default_structure")
|
||||||
|
mass: float = Field(None, alias="mass")
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
|
||||||
|
pubchemCompoundReferences: List[str] = Field([], alias="pubchem_compound_references")
|
||||||
|
reactions: List['SimpleReaction'] = Field([], alias="related_reactions")
|
||||||
|
reviewStatus: str = Field(None, alias="review_status")
|
||||||
|
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
|
||||||
|
smiles: str = Field(None, alias="smiles")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_review_status(obj: CompoundStructure):
|
||||||
|
return 'reviewed' if obj.compound.package.reviewed else 'unreviewed'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_inchi(obj: CompoundStructure):
|
||||||
|
return FormatConverter.InChI(obj.smiles)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_charge(obj: CompoundStructure):
|
||||||
|
print(obj.smiles)
|
||||||
|
print(FormatConverter.charge(obj.smiles))
|
||||||
|
return FormatConverter.charge(obj.smiles)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_formula(obj: CompoundStructure):
|
||||||
|
return FormatConverter.formula(obj.smiles)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_mass(obj: CompoundStructure):
|
||||||
|
return FormatConverter.mass(obj.smiles)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_external_references(obj: CompoundStructure):
|
||||||
|
# TODO
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_halflifes(obj: CompoundStructure):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_pubchem_compound_references(obj: CompoundStructure):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_pathway_scenarios(obj: CompoundStructure):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'scenarioId': 'https://envipath.org/package/5882df9c-dae1-4d80-a40e-db4724271456/scenario/cd8350cd-4249-4111-ba9f-4e2209338501',
|
||||||
|
'scenarioName': 'Fritz, R. & Brauner, A. (1989) - (00004)',
|
||||||
|
'scenarioType': 'Soil'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundStructureWrapper(Schema):
|
||||||
|
structure: List['SimpleCompoundStructure']
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/compound", response={200: CompoundWrapper, 403: Error})
|
||||||
|
def get_compounds(request):
|
||||||
|
qs = Compound.objects.none()
|
||||||
|
for p in PackageManager.get_reviewed_packages():
|
||||||
|
qs |= Compound.objects.filter(package=p)
|
||||||
|
return {'compound': qs}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/compound", response={200: CompoundWrapper, 403: Error})
|
||||||
|
def get_package_compounds(request, package_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return {'compound': Compound.objects.filter(package=p).prefetch_related('package')}
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Compounds for Package with id {package_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}", response={200: CompoundSchema, 403: Error})
|
||||||
|
def get_package_compound(request, package_uuid, compound_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return Compound.objects.get(package=p, uuid=compound_uuid)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Compound with id {compound_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure",
|
||||||
|
response={200: CompoundStructureWrapper, 403: Error})
|
||||||
|
def get_package_compound_structures(request, package_uuid, compound_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return {'structure': Compound.objects.get(package=p, uuid=compound_uuid).structures.all()}
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting CompoundStructures for Compound with id {compound_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure/{uuid:structure_uuid}",
|
||||||
|
response={200: CompoundStructureSchema, 403: Error})
|
||||||
|
def get_package_compound_structure(request, package_uuid, compound_uuid, structure_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return CompoundStructure.objects.get(uuid=structure_uuid,
|
||||||
|
compound=Compound.objects.get(package=p, uuid=compound_uuid))
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting CompoundStructure with id {structure_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
#########
|
||||||
|
# Rules #
|
||||||
|
#########
|
||||||
|
class SimpleRule(SimpleObject):
|
||||||
|
identifier: str = 'rule'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_url(obj: Rule):
|
||||||
|
return obj.url.replace('-ambit-', '-').replace('-rdkit-', '-')
|
||||||
|
|
||||||
|
|
||||||
|
class RuleWrapper(Schema):
|
||||||
|
rule: List['SimpleRule']
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleRuleSchema(Schema):
|
||||||
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
|
description: str = Field(None, alias="description")
|
||||||
|
ecNumbers: List[Dict[str, str]] = Field([], alias="ec_numbers")
|
||||||
|
engine: str = 'ambit'
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
identifier: str = Field(None, alias="identifier")
|
||||||
|
isCompositeRule: bool = False
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
|
||||||
|
productFilterSmarts: str = Field("", alias="product_filter_smarts")
|
||||||
|
productSmarts: str = Field(None, alias="products_smarts")
|
||||||
|
reactantFilterSmarts: str = Field("", alias="reactant_filter_smarts")
|
||||||
|
reactantSmarts: str = Field(None, alias="reactants_smarts")
|
||||||
|
reactions: List['SimpleReaction'] = Field([], alias="related_reactions")
|
||||||
|
reviewStatus: str = Field(None, alias="review_status")
|
||||||
|
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
|
||||||
|
smirks: str = Field("", alias="smirks")
|
||||||
|
# TODO
|
||||||
|
transformations: str = Field("", alias="transformations")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_url(obj: Rule):
|
||||||
|
return obj.url.replace('-ambit-', '-').replace('-rdkit-', '-')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_identifier(obj: Rule):
|
||||||
|
if 'simple-rule' in obj.url:
|
||||||
|
return 'simple-rule'
|
||||||
|
if 'simple-ambit-rule' in obj.url:
|
||||||
|
return 'simple-rule'
|
||||||
|
elif 'parallel-rule' in obj.url:
|
||||||
|
return 'parallel-rule'
|
||||||
|
elif 'sequential-rule' in obj.url:
|
||||||
|
return 'sequential-rule'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_review_status(obj: Rule):
|
||||||
|
return 'reviewed' if obj.package.reviewed else 'unreviewed'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_product_filter_smarts(obj: Rule):
|
||||||
|
return obj.product_filter_smarts if obj.product_filter_smarts else ''
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_reactant_filter_smarts(obj: Rule):
|
||||||
|
return obj.reactant_filter_smarts if obj.reactant_filter_smarts else ''
|
||||||
|
|
||||||
|
|
||||||
|
class CompositeRuleSchema(Schema):
|
||||||
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
|
description: str = Field(None, alias="description")
|
||||||
|
ecNumbers: List[Dict[str, str]] = Field([], alias="ec_numbers")
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
identifier: str = Field(None, alias="identifier")
|
||||||
|
isCompositeRule: bool = True
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
|
||||||
|
productFilterSmarts: str = Field("", alias="product_filter_smarts")
|
||||||
|
reactantFilterSmarts: str = Field("", alias="reactant_filter_smarts")
|
||||||
|
reactions: List['SimpleReaction'] = Field([], alias="related_reactions")
|
||||||
|
reviewStatus: str = Field(None, alias="review_status")
|
||||||
|
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
|
||||||
|
simpleRules: List['SimpleRule'] = Field([], alias="simple_rules")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_ec_numbers(obj: Rule):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_url(obj: Rule):
|
||||||
|
return obj.url.replace('-ambit-', '-').replace('-rdkit-', '-')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_identifier(obj: Rule):
|
||||||
|
if 'simple-rule' in obj.url:
|
||||||
|
return 'simple-rule'
|
||||||
|
if 'simple-ambit-rule' in obj.url:
|
||||||
|
return 'simple-rule'
|
||||||
|
elif 'parallel-rule' in obj.url:
|
||||||
|
return 'parallel-rule'
|
||||||
|
elif 'sequential-rule' in obj.url:
|
||||||
|
return 'sequential-rule'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_review_status(obj: Rule):
|
||||||
|
return 'reviewed' if obj.package.reviewed else 'unreviewed'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_product_filter_smarts(obj: Rule):
|
||||||
|
return obj.product_filter_smarts if obj.product_filter_smarts else ''
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_reactant_filter_smarts(obj: Rule):
|
||||||
|
return obj.reactant_filter_smarts if obj.reactant_filter_smarts else ''
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/rule", response={200: RuleWrapper, 403: Error})
|
||||||
|
def get_rules(request):
|
||||||
|
qs = Rule.objects.none()
|
||||||
|
for p in PackageManager.get_reviewed_packages():
|
||||||
|
qs |= Rule.objects.filter(package=p)
|
||||||
|
return {'rule': qs}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/rule", response={200: RuleWrapper, 403: Error})
|
||||||
|
def get_package_rules(request, package_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return {'rule': Rule.objects.filter(package=p).prefetch_related('package')}
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Rules for Package with id {package_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}",
|
||||||
|
response={200: SimpleRuleSchema | CompositeRuleSchema, 403: Error})
|
||||||
|
def get_package_rule(request, package_uuid, rule_uuid):
|
||||||
|
return _get_package_rule(request, package_uuid, rule_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/simple-rule/{uuid:rule_uuid}",
|
||||||
|
response={200: SimpleRuleSchema | CompositeRuleSchema, 403: Error})
|
||||||
|
def get_package_simple_rule(request, package_uuid, rule_uuid):
|
||||||
|
return _get_package_rule(request, package_uuid, rule_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/parallel-rule/{uuid:rule_uuid}",
|
||||||
|
response={200: SimpleRuleSchema | CompositeRuleSchema, 403: Error})
|
||||||
|
def get_package_parallel_rule(request, package_uuid, rule_uuid):
|
||||||
|
return _get_package_rule(request, package_uuid, rule_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_package_rule(request, package_uuid, rule_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return Rule.objects.get(package=p, uuid=rule_uuid)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Rule with id {rule_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
# POST
|
||||||
|
@router.post("/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error})
|
||||||
|
def post_package_rule(request, package_uuid, rule_uuid, compound: Form[str] = None):
|
||||||
|
return _post_package_rule(request, package_uuid, rule_uuid, compound=compound)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/simple-rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error})
|
||||||
|
def post_package_simple_rule(request, package_uuid, rule_uuid, compound: Form[str] = None):
|
||||||
|
return _post_package_rule(request, package_uuid, rule_uuid, compound=compound)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/parallel-rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error})
|
||||||
|
def post_package_parallel_rule(request, package_uuid, rule_uuid, compound: Form[str] = None):
|
||||||
|
return _post_package_rule(request, package_uuid, rule_uuid, compound=compound)
|
||||||
|
|
||||||
|
|
||||||
|
def _post_package_rule(request, package_uuid, rule_uuid, compound: Form[str]):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
r = Rule.objects.get(package=p, uuid=rule_uuid)
|
||||||
|
|
||||||
|
if compound is not None:
|
||||||
|
if not compound.split():
|
||||||
|
return 400, {'message': 'Compound is empty'}
|
||||||
|
|
||||||
|
product_sets = r.apply(compound)
|
||||||
|
|
||||||
|
res = []
|
||||||
|
for p_set in product_sets:
|
||||||
|
for product in p_set:
|
||||||
|
res.append(product)
|
||||||
|
|
||||||
|
return HttpResponse('\n'.join(res), content_type="text/plain")
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Rule with id {rule_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# Reaction #
|
||||||
|
############
|
||||||
|
class SimpleReaction(SimpleObject):
|
||||||
|
identifier: str = 'reaction'
|
||||||
|
|
||||||
|
|
||||||
|
class ReactionWrapper(Schema):
|
||||||
|
reaction: List['SimpleReaction']
|
||||||
|
|
||||||
|
|
||||||
|
class ReactionCompoundStructure(Schema):
|
||||||
|
compoundName: str = Field(None, alias="name")
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
smiles: str = Field(None, alias="smiles")
|
||||||
|
|
||||||
|
|
||||||
|
class ReactionSchema(Schema):
|
||||||
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
|
description: str = Field(None, alias="description")
|
||||||
|
ecNumbers: List[Dict[str, str]] = Field([], alias="ec_numbers")
|
||||||
|
educts: List['ReactionCompoundStructure'] = Field([], alias="educts")
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
identifier: str = 'reaction'
|
||||||
|
medlineRefs: List[str] = Field([], alias="medline_references")
|
||||||
|
multistep: bool = Field(None, alias="multi_step")
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
|
||||||
|
products: List['ReactionCompoundStructure'] = Field([], alias="products")
|
||||||
|
references: List[Dict[str, List[str]]] = Field([], alias="references")
|
||||||
|
reviewStatus: str = Field(None, alias="review_status")
|
||||||
|
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
|
||||||
|
smirks: str = Field("", alias="smirks")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_smirks(obj: Reaction):
|
||||||
|
return obj.smirks()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_ec_numbers(obj: Reaction):
|
||||||
|
# TODO fetch via scenario EnzymeAI
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_references(obj: Reaction):
|
||||||
|
# TODO
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_medline_references(obj: Reaction):
|
||||||
|
# TODO
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_review_status(obj: Rule):
|
||||||
|
return 'reviewed' if obj.package.reviewed else 'unreviewed'
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/reaction", response={200: ReactionWrapper, 403: Error})
|
||||||
|
def get_reactions(request):
|
||||||
|
qs = Reaction.objects.none()
|
||||||
|
for p in PackageManager.get_reviewed_packages():
|
||||||
|
qs |= Reaction.objects.filter(package=p)
|
||||||
|
return {'reaction': qs}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/reaction", response={200: ReactionWrapper, 403: Error})
|
||||||
|
def get_package_reactions(request, package_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return {'reaction': Reaction.objects.filter(package=p).prefetch_related('package')}
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Reactions for Package with id {package_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/reaction/{uuid:reaction_uuid}", response={200: ReactionSchema, 403: Error})
|
||||||
|
def get_package_reaction(request, package_uuid, reaction_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return Reaction.objects.get(package=p, uuid=reaction_uuid)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Reaction with id {reaction_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# Scenario #
|
||||||
|
############
|
||||||
|
class SimpleScenario(SimpleObject):
|
||||||
|
identifier: str = 'scenario'
|
||||||
|
|
||||||
|
|
||||||
|
class ScenarioWrapper(Schema):
|
||||||
|
scenario: List['SimpleScenario']
|
||||||
|
|
||||||
|
|
||||||
|
class ScenarioSchema(Schema):
|
||||||
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
|
collection: Dict['str', List[Dict[str, Any]]] = Field([], alias="collection")
|
||||||
|
collectionID: Optional[str] = None
|
||||||
|
description: str = Field(None, alias="description")
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
identifier: str = 'scenario'
|
||||||
|
linkedTo: List[Dict[str, str]] = Field({}, alias="linked_to")
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
|
||||||
|
relatedScenarios: List[Dict[str, str]] = Field([], alias="related_scenarios")
|
||||||
|
reviewStatus: str = Field(None, alias="review_status")
|
||||||
|
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
|
||||||
|
type: str = Field(None, alias="scenario_type")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_collection(obj: Scenario):
|
||||||
|
return obj.additional_information
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_review_status(obj: Rule):
|
||||||
|
return 'reviewed' if obj.package.reviewed else 'unreviewed'
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/scenario", response={200: ScenarioWrapper, 403: Error})
|
||||||
|
def get_scenarios(request):
|
||||||
|
qs = Scenario.objects.none()
|
||||||
|
for p in PackageManager.get_reviewed_packages():
|
||||||
|
qs |= Scenario.objects.filter(package=p)
|
||||||
|
return {'scenario': qs}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/scenario", response={200: ScenarioWrapper, 403: Error})
|
||||||
|
def get_package_scenarios(request, package_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return {'scenario': Scenario.objects.filter(package=p).prefetch_related('package')}
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Scenarios for Package with id {package_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/scenario/{uuid:scenario_uuid}", response={200: ScenarioSchema, 403: Error})
|
||||||
|
def get_package_scenario(request, package_uuid, scenario_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return Scenario.objects.get(package=p, uuid=scenario_uuid)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Scenario with id {scenario_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
###########
|
||||||
|
# Pathway #
|
||||||
|
###########
|
||||||
|
class SimplePathway(SimpleObject):
|
||||||
|
identifier: str = 'pathway'
|
||||||
|
|
||||||
|
class PathwayWrapper(Schema):
|
||||||
|
pathway: List['SimplePathway']
|
||||||
|
|
||||||
|
@router.get("/pathway", response={200: PathwayWrapper, 403: Error})
|
||||||
|
def get_pathways(request):
|
||||||
|
qs = Pathway.objects.none()
|
||||||
|
for p in PackageManager.get_reviewed_packages():
|
||||||
|
qs |= Pathway.objects.filter(package=p)
|
||||||
|
return {'pathway': qs}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/pathway", response={200: PathwayWrapper, 403: Error})
|
||||||
|
def get_package_pathways(request, package_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return {'pathway': Pathway.objects.filter(package=p).prefetch_related('package')}
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
'message': f'Getting Pathways for Package with id {package_uuid} failed due to insufficient rights!'}
|
||||||
|
|
||||||
|
|
||||||
|
# @router.get("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}", response={200: Pathway, 403: Error})
|
||||||
|
# def get_package_pathway(request, package_uuid, pathway_uuid):
|
||||||
|
# try:
|
||||||
|
# p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
# return Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
# except ValueError:
|
||||||
|
# return 403, {
|
||||||
|
# 'message': f'Getting Pathway with id {pathway_uuid} failed due to insufficient rights!'}
|
||||||
1392
epdb/logic.py
Normal file
0
epdb/management/__init__.py
Normal file
0
epdb/management/commands/__init__.py
Normal file
205
epdb/management/commands/bootstrap.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.logic import UserManager, GroupManager, PackageManager, SettingManager
|
||||||
|
from epdb.models import UserSettingPermission, MLRelativeReasoning, EnviFormer, Permission, User, ExternalDatabase
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
def create_users(self):
|
||||||
|
|
||||||
|
if not User.objects.filter(email='anon@lorsba.ch').exists():
|
||||||
|
anon = UserManager.create_user("anonymous", "anon@lorsba.ch", "SuperSafe", is_active=True,
|
||||||
|
add_to_group=False, set_setting=False)
|
||||||
|
else:
|
||||||
|
anon = User.objects.get(email='anon@lorsba.ch')
|
||||||
|
|
||||||
|
if not User.objects.filter(email='admin@lorsba.ch').exists():
|
||||||
|
admin = UserManager.create_user("admin", "admin@lorsba.ch", "SuperSafe", is_active=True, add_to_group=False,
|
||||||
|
set_setting=False)
|
||||||
|
admin.is_staff = True
|
||||||
|
admin.is_superuser = True
|
||||||
|
admin.save()
|
||||||
|
else:
|
||||||
|
admin = User.objects.get(email='admin@lorsba.ch')
|
||||||
|
|
||||||
|
g = GroupManager.create_group(admin, 'enviPath Users', 'All enviPath Users')
|
||||||
|
g.public = True
|
||||||
|
g.save()
|
||||||
|
|
||||||
|
g.user_member.add(anon)
|
||||||
|
g.save()
|
||||||
|
|
||||||
|
anon.default_group = g
|
||||||
|
anon.save()
|
||||||
|
|
||||||
|
admin.default_group = g
|
||||||
|
admin.save()
|
||||||
|
|
||||||
|
if not User.objects.filter(email='jebus@lorsba.ch').exists():
|
||||||
|
jebus = UserManager.create_user("jebus", "jebus@lorsba.ch", "SuperSafe", is_active=True, add_to_group=False,
|
||||||
|
set_setting=False)
|
||||||
|
jebus.is_staff = True
|
||||||
|
jebus.is_superuser = True
|
||||||
|
jebus.save()
|
||||||
|
else:
|
||||||
|
jebus = User.objects.get(email='jebus@lorsba.ch')
|
||||||
|
|
||||||
|
g.user_member.add(jebus)
|
||||||
|
g.save()
|
||||||
|
|
||||||
|
jebus.default_group = g
|
||||||
|
jebus.save()
|
||||||
|
|
||||||
|
return anon, admin, g, jebus
|
||||||
|
|
||||||
|
def import_package(self, data, owner):
|
||||||
|
return PackageManager.import_package(data, owner, keep_ids=True, add_import_timestamp=False)
|
||||||
|
|
||||||
|
def create_default_setting(self, owner, packages):
|
||||||
|
s = SettingManager.create_setting(
|
||||||
|
owner,
|
||||||
|
name='Global Default Setting',
|
||||||
|
description='Global Default Setting containing BBD Rules and Max 30 Nodes and Max Depth of 8',
|
||||||
|
max_nodes=30,
|
||||||
|
max_depth=5,
|
||||||
|
rule_packages=packages,
|
||||||
|
model=None,
|
||||||
|
model_threshold=None
|
||||||
|
)
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
def populate_common_external_databases(self):
|
||||||
|
"""
|
||||||
|
Helper function to populate common external databases.
|
||||||
|
This can be called from a Django management command.
|
||||||
|
"""
|
||||||
|
databases = [
|
||||||
|
{
|
||||||
|
'name': 'PubChem Compound',
|
||||||
|
'full_name': 'PubChem Compound Database',
|
||||||
|
'description': 'Chemical database of small organic molecules',
|
||||||
|
'base_url': 'https://pubchem.ncbi.nlm.nih.gov',
|
||||||
|
'url_pattern': 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'PubChem Substance',
|
||||||
|
'full_name': 'PubChem Substance Database',
|
||||||
|
'description': 'Database of chemical substances',
|
||||||
|
'base_url': 'https://pubchem.ncbi.nlm.nih.gov',
|
||||||
|
'url_pattern': 'https://pubchem.ncbi.nlm.nih.gov/substance/{id}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'ChEBI',
|
||||||
|
'full_name': 'Chemical Entities of Biological Interest',
|
||||||
|
'description': 'Dictionary of molecular entities',
|
||||||
|
'base_url': 'https://www.ebi.ac.uk/chebi',
|
||||||
|
'url_pattern': 'https://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:{id}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'RHEA',
|
||||||
|
'full_name': 'RHEA Reaction Database',
|
||||||
|
'description': 'Comprehensive resource of biochemical reactions',
|
||||||
|
'base_url': 'https://www.rhea-db.org',
|
||||||
|
'url_pattern': 'https://www.rhea-db.org/rhea/{id}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'CAS',
|
||||||
|
'full_name': 'Chemical Abstracts Service Registry',
|
||||||
|
'description': 'Registry of chemical substances',
|
||||||
|
'base_url': 'https://www.cas.org',
|
||||||
|
'url_pattern': None # CAS doesn't have a free public URL pattern
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'KEGG Reaction',
|
||||||
|
'full_name': 'KEGG Reaction Database',
|
||||||
|
'description': 'Database of biochemical reactions',
|
||||||
|
'base_url': 'https://www.genome.jp',
|
||||||
|
'url_pattern': 'https://www.genome.jp/entry/reaction+{id}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'MetaCyc',
|
||||||
|
'full_name': 'MetaCyc Metabolic Pathway Database',
|
||||||
|
'description': 'Database of metabolic pathways and enzymes',
|
||||||
|
'base_url': 'https://metacyc.org',
|
||||||
|
'url_pattern': None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'UniProt',
|
||||||
|
'full_name': 'MetaCyc Metabolic Pathway Database',
|
||||||
|
'description': 'UniProt is a freely accessible database of protein sequence and functional information',
|
||||||
|
'base_url': 'https://www.uniprot.org',
|
||||||
|
'url_pattern': 'https://www.uniprot.org/uniprotkb?query="{id}"'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for db_info in databases:
|
||||||
|
ExternalDatabase.objects.get_or_create(
|
||||||
|
name=db_info['name'],
|
||||||
|
defaults=db_info
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
# Create users
|
||||||
|
anon, admin, g, jebus = self.create_users()
|
||||||
|
|
||||||
|
# Import Packages
|
||||||
|
packages = [
|
||||||
|
'EAWAG-BBD.json',
|
||||||
|
'EAWAG-SOIL.json',
|
||||||
|
'EAWAG-SLUDGE.json',
|
||||||
|
'EAWAG-SEDIMENT.json',
|
||||||
|
]
|
||||||
|
|
||||||
|
mapping = {}
|
||||||
|
for p in packages:
|
||||||
|
print(f"Importing {p}...")
|
||||||
|
package_data = json.loads(open(s.BASE_DIR / 'fixtures' / 'packages' / '2025-07-18' / p).read())
|
||||||
|
imported_package = self.import_package(package_data, admin)
|
||||||
|
mapping[p.replace('.json', '')] = imported_package
|
||||||
|
|
||||||
|
setting = self.create_default_setting(admin, [mapping['EAWAG-BBD']])
|
||||||
|
setting.public = True
|
||||||
|
setting.save()
|
||||||
|
setting.make_global_default()
|
||||||
|
|
||||||
|
for u in [anon, jebus]:
|
||||||
|
u.default_setting = setting
|
||||||
|
u.save()
|
||||||
|
|
||||||
|
usp = UserSettingPermission()
|
||||||
|
usp.user = u
|
||||||
|
usp.setting = setting
|
||||||
|
usp.permission = Permission.READ[0]
|
||||||
|
usp.save()
|
||||||
|
|
||||||
|
# Create Model Package
|
||||||
|
pack = PackageManager.create_package(admin, "Public Prediction Models",
|
||||||
|
"Package to make Prediction Models publicly available")
|
||||||
|
pack.reviewed = True
|
||||||
|
pack.save()
|
||||||
|
|
||||||
|
# Create RR
|
||||||
|
ml_model = MLRelativeReasoning.create(
|
||||||
|
package=pack,
|
||||||
|
rule_packages=[mapping['EAWAG-BBD']],
|
||||||
|
data_packages=[mapping['EAWAG-BBD']],
|
||||||
|
eval_packages=[],
|
||||||
|
threshold=0.5,
|
||||||
|
name='ECC - BBD - T0.5',
|
||||||
|
description='ML Relative Reasoning',
|
||||||
|
)
|
||||||
|
|
||||||
|
ml_model.build_dataset()
|
||||||
|
ml_model.build_model()
|
||||||
|
# ml_model.evaluate_model()
|
||||||
|
|
||||||
|
# If available create EnviFormerModel
|
||||||
|
if s.ENVIFORMER_PRESENT:
|
||||||
|
enviFormer_model = EnviFormer.create(pack, 'EnviFormer - T0.5', 'EnviFormer Model with Threshold 0.5', 0.5)
|
||||||
27
epdb/management/commands/import_package.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from epdb.logic import PackageManager
|
||||||
|
from epdb.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--data',
|
||||||
|
type=str,
|
||||||
|
help='Path of the Package to import.',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--owner',
|
||||||
|
type=str,
|
||||||
|
help='Username of the desired Owner.',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
owner = User.objects.get(username=options['owner'])
|
||||||
|
package_data = json.load(open(options['data']))
|
||||||
|
PackageManager.import_package(package_data, owner)
|
||||||
50
epdb/management/commands/localize_urls.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from django.apps import apps
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from django.db.models import F, Value
|
||||||
|
from django.db.models.functions import Replace
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--old',
|
||||||
|
type=str,
|
||||||
|
help='Old Host, most likely https://envipath.org/',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--new',
|
||||||
|
type=str,
|
||||||
|
help='New Host, most likely http://localhost:8000/',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
MODELS = [
|
||||||
|
'User',
|
||||||
|
'Group',
|
||||||
|
'Package',
|
||||||
|
'Compound',
|
||||||
|
'CompoundStructure',
|
||||||
|
'Pathway',
|
||||||
|
'Edge',
|
||||||
|
'Node',
|
||||||
|
'Reaction',
|
||||||
|
'SimpleAmbitRule',
|
||||||
|
'SimpleRDKitRule',
|
||||||
|
'ParallelRule',
|
||||||
|
'SequentialRule',
|
||||||
|
'Scenario',
|
||||||
|
'Setting',
|
||||||
|
'MLRelativeReasoning',
|
||||||
|
'EnviFormer',
|
||||||
|
'ApplicabilityDomain',
|
||||||
|
]
|
||||||
|
for model in MODELS:
|
||||||
|
obj_cls = apps.get_model("epdb", model)
|
||||||
|
print(f"Localizing urls for {model}")
|
||||||
|
obj_cls.objects.update(
|
||||||
|
url=Replace(F('url'), Value(options['old']), Value(options['new']))
|
||||||
|
)
|
||||||
0
epdb/middleware/__init__.py
Normal file
24
epdb/middleware/login_required_middleware.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
class LoginRequiredMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
self.exempt_urls = [
|
||||||
|
reverse('login'),
|
||||||
|
reverse('logout'),
|
||||||
|
reverse('admin:login'),
|
||||||
|
reverse('admin:index'),
|
||||||
|
] + getattr(settings, 'LOGIN_EXEMPT_URLS', [])
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
path = request.path_info
|
||||||
|
if not any(path.startswith(url) for url in self.exempt_urls):
|
||||||
|
if request.method == 'GET':
|
||||||
|
if request.get_full_path() and request.get_full_path() != '/':
|
||||||
|
return redirect(f"{settings.LOGIN_URL}?next={quote(request.get_full_path())}")
|
||||||
|
return redirect(settings.LOGIN_URL)
|
||||||
|
return self.get_response(request)
|
||||||
594
epdb/migrations/0001_initial.py
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-07-22 20:58
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Compound',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EPModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Permission',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='License',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('link', models.URLField(verbose_name='link')),
|
||||||
|
('image_link', models.URLField(verbose_name='Image link')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rule',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='User',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
|
('email', models.EmailField(max_length=254, unique=True)),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'user',
|
||||||
|
'verbose_name_plural': 'users',
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='APIToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('hashed_key', models.CharField(max_length=128, unique=True)),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('expires_at', models.DateTimeField(blank=True, default=datetime.datetime(2025, 10, 20, 20, 58, 48, 351675, tzinfo=datetime.timezone.utc), null=True)),
|
||||||
|
('name', models.CharField(blank=True, help_text='Optional name for the token', max_length=100)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CompoundStructure',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('smiles', models.TextField(verbose_name='SMILES')),
|
||||||
|
('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')),
|
||||||
|
('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')),
|
||||||
|
('normalized_structure', models.BooleanField(default=False)),
|
||||||
|
('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='default_structure',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Edge',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EnviFormer',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PluginModel',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RuleBaseRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Group',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(verbose_name='Group name')),
|
||||||
|
('public', models.BooleanField(default=False, verbose_name='Public Group')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('group_member', models.ManyToManyField(related_name='groups_in_group', to='epdb.group', verbose_name='Group member')),
|
||||||
|
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')),
|
||||||
|
('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_group',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Node',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('depth', models.IntegerField(verbose_name='Node depth')),
|
||||||
|
('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')),
|
||||||
|
('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')),
|
||||||
|
('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='end_nodes',
|
||||||
|
field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='start_nodes',
|
||||||
|
field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Package',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')),
|
||||||
|
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='epmodel',
|
||||||
|
name='package',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='package',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_package',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.package', verbose_name='Default Package'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SequentialRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='package',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='polymorphic_ctype',
|
||||||
|
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Pathway',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='pathway',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='pathway',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Reaction',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('multi_step', models.BooleanField(verbose_name='Multistep Reaction')),
|
||||||
|
('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')),
|
||||||
|
('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
||||||
|
('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')),
|
||||||
|
('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='edge_label',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Scenario',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('scenario_date', models.CharField(default='No date', max_length=256)),
|
||||||
|
('scenario_type', models.CharField(default='Not specified', max_length=256)),
|
||||||
|
('additional_information', models.JSONField(verbose_name='Additional Information')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
||||||
|
('parent', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.scenario')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Setting',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('public', models.BooleanField(default=False)),
|
||||||
|
('global_default', models.BooleanField(default=False)),
|
||||||
|
('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')),
|
||||||
|
('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')),
|
||||||
|
('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')),
|
||||||
|
('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')),
|
||||||
|
('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to='epdb.package', verbose_name='Setting Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='setting',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_setting',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MLRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
|
||||||
|
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('data_packages', models.ManyToManyField(related_name='data_packages', to='epdb.package', verbose_name='Data Packages')),
|
||||||
|
('eval_packages', models.ManyToManyField(related_name='eval_packages', to='epdb.package', verbose_name='Evaluation Packages')),
|
||||||
|
('rule_packages', models.ManyToManyField(related_name='rule_packages', to='epdb.package', verbose_name='Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicabilityDomain',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('num_neighbours', models.FloatField(default=5)),
|
||||||
|
('reliability_threshold', models.FloatField(default=0.5)),
|
||||||
|
('local_compatibilty_threshold', models.FloatField(default=0.5)),
|
||||||
|
('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleAmbitRule',
|
||||||
|
fields=[
|
||||||
|
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
||||||
|
('smirks', models.TextField(verbose_name='SMIRKS')),
|
||||||
|
('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')),
|
||||||
|
('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.simplerule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleRDKitRule',
|
||||||
|
fields=[
|
||||||
|
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
||||||
|
('reaction_smarts', models.TextField(verbose_name='SMIRKS')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.simplerule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SequentialRuleOrdering',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('order_index', models.IntegerField()),
|
||||||
|
('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')),
|
||||||
|
('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sequentialrule',
|
||||||
|
name='simple_rules',
|
||||||
|
field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ParallelRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='compound',
|
||||||
|
unique_together={('uuid', 'package')},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GroupPackagePermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('package', 'group')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserPackagePermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('package', 'user')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserSettingPermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('setting', 'user')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-25 18:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('epdb', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalDatabase',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')),
|
||||||
|
('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||||
|
('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')),
|
||||||
|
('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'External Database',
|
||||||
|
'verbose_name_plural': 'External Databases',
|
||||||
|
'db_table': 'epdb_external_database',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='apitoken',
|
||||||
|
options={'ordering': ['-created'], 'verbose_name': 'API Token', 'verbose_name_plural': 'API Tokens'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='edge',
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='edge',
|
||||||
|
name='polymorphic_ctype',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='is_active',
|
||||||
|
field=models.BooleanField(default=True, help_text='Whether this token is active'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='modified',
|
||||||
|
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='functional_groups',
|
||||||
|
field=models.JSONField(blank=True, default=dict, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='app_domain',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='created',
|
||||||
|
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='expires_at',
|
||||||
|
field=models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='hashed_key',
|
||||||
|
field=models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(help_text='Descriptive name for this token', max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='num_neighbours',
|
||||||
|
field=models.IntegerField(default=5),
|
||||||
|
),
|
||||||
|
migrations.AlterModelTable(
|
||||||
|
name='apitoken',
|
||||||
|
table='epdb_api_token',
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalIdentifier',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('object_id', models.IntegerField()),
|
||||||
|
('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')),
|
||||||
|
('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')),
|
||||||
|
('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')),
|
||||||
|
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||||
|
('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'External Identifier',
|
||||||
|
'verbose_name_plural': 'External Identifiers',
|
||||||
|
'db_table': 'epdb_external_identifier',
|
||||||
|
'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')],
|
||||||
|
'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,228 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-26 17:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def populate_url(apps, schema_editor):
|
||||||
|
MODELS = [
|
||||||
|
'User',
|
||||||
|
'Group',
|
||||||
|
'Package',
|
||||||
|
'Compound',
|
||||||
|
'CompoundStructure',
|
||||||
|
'Pathway',
|
||||||
|
'Edge',
|
||||||
|
'Node',
|
||||||
|
'Reaction',
|
||||||
|
'SimpleAmbitRule',
|
||||||
|
'SimpleRDKitRule',
|
||||||
|
'ParallelRule',
|
||||||
|
'SequentialRule',
|
||||||
|
'Scenario',
|
||||||
|
'Setting',
|
||||||
|
'MLRelativeReasoning',
|
||||||
|
'EnviFormer',
|
||||||
|
'ApplicabilityDomain',
|
||||||
|
]
|
||||||
|
for model in MODELS:
|
||||||
|
obj_cls = apps.get_model("epdb", model)
|
||||||
|
for obj in obj_cls.objects.all():
|
||||||
|
obj.url = assemble_url(obj)
|
||||||
|
if obj.url is None:
|
||||||
|
raise ValueError(f"Could not assemble url for {obj}")
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
def assemble_url(obj):
|
||||||
|
from django.conf import settings as s
|
||||||
|
match obj.__class__.__name__:
|
||||||
|
case 'User':
|
||||||
|
return '{}/user/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case 'Group':
|
||||||
|
return '{}/group/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case 'Package':
|
||||||
|
return '{}/package/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case 'Compound':
|
||||||
|
return '{}/compound/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'CompoundStructure':
|
||||||
|
return '{}/structure/{}'.format(obj.compound.url, obj.uuid)
|
||||||
|
case 'SimpleAmbitRule':
|
||||||
|
return '{}/simple-ambit-rule/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'SimpleRDKitRule':
|
||||||
|
return '{}/simple-rdkit-rule/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'ParallelRule':
|
||||||
|
return '{}/parallel-rule/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'SequentialRule':
|
||||||
|
return '{}/sequential-rule/{}'.format(obj.compound.url, obj.uuid)
|
||||||
|
case 'Reaction':
|
||||||
|
return '{}/reaction/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'Pathway':
|
||||||
|
return '{}/pathway/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'Node':
|
||||||
|
return '{}/node/{}'.format(obj.pathway.url, obj.uuid)
|
||||||
|
case 'Edge':
|
||||||
|
return '{}/edge/{}'.format(obj.pathway.url, obj.uuid)
|
||||||
|
case 'MLRelativeReasoning':
|
||||||
|
return '{}/model/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'EnviFormer':
|
||||||
|
return '{}/model/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'ApplicabilityDomain':
|
||||||
|
return '{}/model/{}/applicability-domain/{}'.format(obj.model.package.url, obj.model.uuid, obj.uuid)
|
||||||
|
case 'Scenario':
|
||||||
|
return '{}/scenario/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'Setting':
|
||||||
|
return '{}/setting/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown model {obj.__class__.__name__}")
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0002_externaldatabase_alter_apitoken_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='epmodel',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='group',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='package',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='scenario',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='setting',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.RunPython(populate_url, reverse_code=migrations.RunPython.noop),
|
||||||
|
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='compound',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='edge',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='epmodel',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='group',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='node',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='package',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='scenario',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='setting',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
epdb/migrations/__init__.py
Normal file
2621
epdb/models.py
Normal file
20
epdb/signals.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from django.db import transaction
|
||||||
|
from django.db.models.signals import pre_delete
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from epdb.models import Node, Edge
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_delete, sender=Node)
|
||||||
|
@transaction.atomic
|
||||||
|
def delete_orphan_edges(sender, instance, **kwargs):
|
||||||
|
# check if the node that is about to be deleted is the only start node
|
||||||
|
for edge in Edge.objects.filter(start_nodes=instance):
|
||||||
|
if edge.start_nodes.count() == 1:
|
||||||
|
edge.delete()
|
||||||
|
|
||||||
|
# same for end_nodes
|
||||||
|
for edge in Edge.objects.filter(end_nodes=instance):
|
||||||
|
# check if the node that is about to be deleted is the only start node
|
||||||
|
if edge.end_nodes.count() == 1:
|
||||||
|
edge.delete()
|
||||||
81
epdb/tasks.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from celery.signals import worker_process_init
|
||||||
|
from celery import shared_task
|
||||||
|
from epdb.models import Pathway, Node, Edge, EPModel, Setting
|
||||||
|
from epdb.logic import SPathway
|
||||||
|
|
||||||
|
from utilities.chem import FormatConverter
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue='background')
|
||||||
|
def mul(a, b):
|
||||||
|
return a * b
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue='predict')
|
||||||
|
def predict_simple(model_pk: int, smiles: str):
|
||||||
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
|
res = mod.predict(smiles)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue='background')
|
||||||
|
def send_registration_mail(user_pk: int):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue='model')
|
||||||
|
def build_model(model_pk: int):
|
||||||
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
|
mod.build_dataset()
|
||||||
|
mod.build_model()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue='model')
|
||||||
|
def evaluate_model(model_pk: int):
|
||||||
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
|
mod.evaluate_model()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue='predict')
|
||||||
|
def predict(pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_pk: Optional[int] = None) -> Pathway:
|
||||||
|
pw = Pathway.objects.get(id=pw_pk)
|
||||||
|
setting = Setting.objects.get(id=pred_setting_pk)
|
||||||
|
|
||||||
|
pw.kv.update(**{'status': 'running'})
|
||||||
|
pw.save()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# regular prediction
|
||||||
|
if limit is not None:
|
||||||
|
spw = SPathway(prediction_setting=setting, persist=pw)
|
||||||
|
level = 0
|
||||||
|
while not spw.done:
|
||||||
|
spw.predict_step(from_depth=level)
|
||||||
|
level += 1
|
||||||
|
|
||||||
|
# break in case we are in incremental mode
|
||||||
|
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()
|
||||||
0
epdb/templatetags/__init__.py
Normal file
7
epdb/templatetags/envipytags.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def classname(obj):
|
||||||
|
return obj.__class__.__name__
|
||||||
3
epdb/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
82
epdb/urls.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
from django.urls import path, re_path
|
||||||
|
|
||||||
|
from . import views as v
|
||||||
|
# from sesame.views import LoginView
|
||||||
|
|
||||||
|
UUID = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# Sesame
|
||||||
|
# path("login/", v.EmailLoginView.as_view(), name="email_login"),
|
||||||
|
# path("login/auth/", LoginView.as_view(), name="login"),
|
||||||
|
|
||||||
|
# Home
|
||||||
|
re_path(r'^$', v.index, name='index'),
|
||||||
|
|
||||||
|
re_path(r'^login', v.login, name='login'),
|
||||||
|
re_path(r'^logout', v.logout, name='logout'),
|
||||||
|
|
||||||
|
# Top level urls
|
||||||
|
re_path(r'^package$', v.packages, name='packages'),
|
||||||
|
re_path(r'^compound$', v.compounds, name='compounds'),
|
||||||
|
re_path(r'^rule$', v.rules, name='rules'),
|
||||||
|
re_path(r'^reaction$', v.reactions, name='reactions'),
|
||||||
|
re_path(r'^pathway$', v.pathways, name='pathways'),
|
||||||
|
re_path(r'^scenario$', v.scenarios, name='scenarios'),
|
||||||
|
re_path(r'^model$', v.models, name='model'),
|
||||||
|
re_path(r'^user$', v.users, name='users'),
|
||||||
|
re_path(r'^group$', v.groups, name='groups'),
|
||||||
|
re_path(r'^search$', v.search, name='search'),
|
||||||
|
|
||||||
|
|
||||||
|
# User Detail
|
||||||
|
re_path(rf'^user/(?P<user_uuid>{UUID})', v.user, name='user'),
|
||||||
|
# Group Detail
|
||||||
|
re_path(rf'^group/(?P<group_uuid>{UUID})$', v.group, name='group_detail'),
|
||||||
|
|
||||||
|
# "in package" urls
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})$', v.package, name='package_detail'),
|
||||||
|
# Compound
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/compound$', v.package_compounds, name='package compound list'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})$', v.package_compound, name='package compound detail'),
|
||||||
|
# Compound Structure
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})/structure$', v.package_compound_structures, name='package compound structure list'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})/structure/(?P<structure_uuid>{UUID})$', v.package_compound_structure, name='package compound structure detail'),
|
||||||
|
# Rule
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/rule$', v.package_rules, name='package rule list'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})$', v.package_rule, name='package rule detail'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})$', v.package_rule, name='package rule detail'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$', v.package_rule, name='package rule detail'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})$', v.package_rule, name='package rule detail'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/sequential-rule/(?P<rule_uuid>{UUID})$', v.package_rule, name='package rule detail'),
|
||||||
|
# Reaction
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/reaction$', v.package_reactions, name='package reaction list'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/reaction/(?P<reaction_uuid>{UUID})$', v.package_reaction, name='package reaction detail'),
|
||||||
|
# # Pathway
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway$', v.package_pathways, name='package pathway list'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})$', v.package_pathway, name='package pathway detail'),
|
||||||
|
# Pathway Nodes
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$', v.package_pathway_nodes, name='package pathway node list'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node/(?P<node_uuid>{UUID})$', v.package_pathway_node, name='package pathway node detail'),
|
||||||
|
# Pathway Edges
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge$', v.package_pathway_edges, name='package pathway edge list'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge/(?P<edge_uuid>{UUID})$', v.package_pathway_edge, name='package pathway edge detail'),
|
||||||
|
# Scenario
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario$', v.package_scenarios, name='package scenario list'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario/(?P<scenario_uuid>{UUID})$', v.package_scenario, name='package scenario detail'),
|
||||||
|
# Model
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/model$', v.package_models, name='package model list'),
|
||||||
|
re_path(rf'^package/(?P<package_uuid>{UUID})/model/(?P<model_uuid>{UUID})$', v.package_model,name='package model detail'),
|
||||||
|
|
||||||
|
re_path(r'^setting$', v.settings, name='settings'),
|
||||||
|
re_path(rf'^setting/(?P<setting_uuid>{UUID})', v.setting, name='setting'),
|
||||||
|
|
||||||
|
re_path(r'^indigo/info$', v.indigo, name='indigo_info'),
|
||||||
|
re_path(r'^indigo/aromatize$', v.aromatize, name='indigo_aromatize'),
|
||||||
|
re_path(r'^indigo/dearomatize$', v.dearomatize, name='indigo_dearomatize'),
|
||||||
|
re_path(r'^indigo/layout$', v.layout, name='indigo_layout'),
|
||||||
|
|
||||||
|
re_path(r'^depict$', v.depict, name='depict'),
|
||||||
|
|
||||||
|
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
|
||||||
|
]
|
||||||
2234
epdb/views.py
Normal file
439315
fixtures/EAWAG-BBD.json
Normal file
125432
fixtures/EAWAG-SLUDGE.json
Normal file
1572257
fixtures/EAWAG-SOIL.json
Normal file
BIN
fixtures/ambit_rules.json.gz
Normal file
BIN
fixtures/bootstrap.json.gz
Normal file
1
fixtures/migration_status_per_rule.json
Normal file
22
manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
0
migration/__init__.py
Normal file
3
migration/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
migration/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class MigrationConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'migration'
|
||||||
0
migration/migrations/__init__.py
Normal file
3
migration/models.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
migration/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
15
migration/urls.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
|
from . import views as v
|
||||||
|
|
||||||
|
UUID = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(rf'^migration$', v.migration, name='migration'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-rule/(?P<rule_uuid>{UUID})$',v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/sequential-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
]
|
||||||
199
migration/views.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import gzip
|
||||||
|
import json
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from epdb.logic import PackageManager
|
||||||
|
from epdb.models import Rule
|
||||||
|
from epdb.views import get_base_context, _anonymous_or_real
|
||||||
|
from utilities.chem import FormatConverter
|
||||||
|
|
||||||
|
|
||||||
|
def migration(request):
|
||||||
|
if request.method == 'GET':
|
||||||
|
context = get_base_context(request)
|
||||||
|
|
||||||
|
if os.path.exists(s.BASE_DIR / 'fixtures' / 'migration_status_per_rule.json') and request.GET.get(
|
||||||
|
"force") is None:
|
||||||
|
migration_status = json.load(open(s.BASE_DIR / 'fixtures' / 'migration_status_per_rule.json'))
|
||||||
|
else:
|
||||||
|
data = json.load(gzip.open(s.BASE_DIR / 'fixtures' / 'ambit_rules.json.gz', 'rb'))
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
success = 0
|
||||||
|
error = 0
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
num_keys = len(data.keys())
|
||||||
|
for i, bt_rule_name in enumerate(data.keys()):
|
||||||
|
print(f"{i + 1}/{num_keys}")
|
||||||
|
bt_rule = data[bt_rule_name]
|
||||||
|
smirks = bt_rule['smirks']
|
||||||
|
|
||||||
|
all_prods = set()
|
||||||
|
|
||||||
|
res = True
|
||||||
|
|
||||||
|
for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']):
|
||||||
|
|
||||||
|
products = FormatConverter.apply(comp['smiles'], smirks)
|
||||||
|
|
||||||
|
all_rdkit_prods = []
|
||||||
|
for ps in products:
|
||||||
|
for p in ps:
|
||||||
|
all_rdkit_prods.append(p)
|
||||||
|
|
||||||
|
all_rdkit_prods = list(set(all_rdkit_prods))
|
||||||
|
|
||||||
|
ambit_smiles, ambit_errors = FormatConverter.sanitize_smiles(ambit_prod)
|
||||||
|
rdkit_smiles, rdkit_errors = FormatConverter.sanitize_smiles(all_rdkit_prods)
|
||||||
|
|
||||||
|
for x in ambit_smiles:
|
||||||
|
all_prods.add(x)
|
||||||
|
|
||||||
|
# TODO mode "intersection"
|
||||||
|
# partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0)
|
||||||
|
# FAILED (failures=37)
|
||||||
|
|
||||||
|
# TODO mode = "full ambit"
|
||||||
|
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles)
|
||||||
|
# FAILED (failures=46)
|
||||||
|
|
||||||
|
# TODO mode = "equality"
|
||||||
|
partial_res = set(ambit_smiles) == set(rdkit_smiles)
|
||||||
|
# FAILED (failures=69)
|
||||||
|
|
||||||
|
res &= partial_res
|
||||||
|
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
'name': bt_rule_name,
|
||||||
|
'id': bt_rule['id'].split('/')[-1],
|
||||||
|
'url': bt_rule['id'],
|
||||||
|
'status': res,
|
||||||
|
'detail_url': s.SERVER_URL + '/migration/' + bt_rule['id'].replace('https://envipath.org/', '')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if res:
|
||||||
|
success += 1
|
||||||
|
else:
|
||||||
|
error += 1
|
||||||
|
|
||||||
|
total += 1
|
||||||
|
|
||||||
|
results = sorted(results, key=lambda x: (x['status'], x['name']))
|
||||||
|
|
||||||
|
migration_status = {
|
||||||
|
'results': results,
|
||||||
|
'success': success,
|
||||||
|
'error': error,
|
||||||
|
'total': total
|
||||||
|
}
|
||||||
|
|
||||||
|
json.dump(migration_status, open(s.BASE_DIR / 'fixtures' / 'migration_status_per_rule.json', 'w'))
|
||||||
|
|
||||||
|
for r in migration_status['results']:
|
||||||
|
r['detail_url'] = r['detail_url'].replace('http://localhost:8000', s.SERVER_URL)
|
||||||
|
|
||||||
|
context.update(**migration_status)
|
||||||
|
|
||||||
|
return render(request, 'migration.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def migration_detail(request, package_uuid, rule_uuid):
|
||||||
|
current_user = _anonymous_or_real(request)
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
context = get_base_context(request)
|
||||||
|
|
||||||
|
p = PackageManager.get_package_by_id(current_user, package_uuid)
|
||||||
|
rule = Rule.objects.get(package=p, uuid=rule_uuid)
|
||||||
|
bt_rule_name = rule.name
|
||||||
|
|
||||||
|
data = json.load(gzip.open(s.BASE_DIR / 'fixtures' / 'ambit_rules.json.gz', 'rb'))
|
||||||
|
|
||||||
|
bt_rule = data[bt_rule_name]
|
||||||
|
smirks = bt_rule['smirks']
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
res = True
|
||||||
|
|
||||||
|
all_prods = set()
|
||||||
|
for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']):
|
||||||
|
# if comp['smiles'] != 'CC1=C(C(=C(C=N1)CO)C=O)O':
|
||||||
|
# continue
|
||||||
|
|
||||||
|
products = FormatConverter.apply(comp['smiles'], smirks)
|
||||||
|
|
||||||
|
all_rdkit_prods = []
|
||||||
|
for ps in products:
|
||||||
|
for p in ps:
|
||||||
|
all_rdkit_prods.append(p)
|
||||||
|
|
||||||
|
all_rdkit_prods = list(set(all_rdkit_prods))
|
||||||
|
|
||||||
|
ambit_smiles, ambit_errors = FormatConverter.sanitize_smiles(ambit_prod)
|
||||||
|
rdkit_smiles, rdkit_errors = FormatConverter.sanitize_smiles(all_rdkit_prods)
|
||||||
|
|
||||||
|
for x in ambit_smiles:
|
||||||
|
all_prods.add(x)
|
||||||
|
|
||||||
|
# TODO mode "intersection"
|
||||||
|
# partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0)
|
||||||
|
# FAILED (failures=37)
|
||||||
|
|
||||||
|
# TODO mode = "full ambit"
|
||||||
|
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles)
|
||||||
|
# FAILED (failures=46)
|
||||||
|
|
||||||
|
# TODO mode = "equality"
|
||||||
|
partial_res = set(ambit_smiles) == set(rdkit_smiles)
|
||||||
|
# FAILED (failures=69)
|
||||||
|
|
||||||
|
#
|
||||||
|
if len(ambit_smiles) or len(rdkit_smiles):
|
||||||
|
temp = {
|
||||||
|
'url': comp['id'],
|
||||||
|
'id': comp['id'].split('/')[-1],
|
||||||
|
'name': comp['name'],
|
||||||
|
'initial_smiles': comp['smiles'],
|
||||||
|
'ambit_smiles': sorted(list(ambit_smiles)),
|
||||||
|
'rdkit_smiles': sorted(list(rdkit_smiles)),
|
||||||
|
'status': set(ambit_smiles) == set(rdkit_smiles),
|
||||||
|
}
|
||||||
|
|
||||||
|
if set(ambit_smiles) != set(rdkit_smiles):
|
||||||
|
detail = f"""
|
||||||
|
BT: {bt_rule_name}
|
||||||
|
SMIRKS: {bt_rule['smirks']}
|
||||||
|
Compound: {comp['smiles']}
|
||||||
|
Compound URL: {comp['id']}
|
||||||
|
Num ambit: {len(set(ambit_smiles))}
|
||||||
|
Num rdkit: {len(set(rdkit_smiles))}
|
||||||
|
Num Intersection A: {len(set(ambit_smiles).intersection(set(rdkit_smiles)))}
|
||||||
|
Num Intersection B: {len(set(rdkit_smiles).intersection(set(ambit_smiles)))}
|
||||||
|
Difference A: {set(ambit_smiles).difference(set(rdkit_smiles))}
|
||||||
|
Difference B: {set(rdkit_smiles).difference(set(ambit_smiles))}
|
||||||
|
ambit products: {ambit_smiles}
|
||||||
|
rdkit products: {rdkit_smiles}
|
||||||
|
ambit_errors: {ambit_errors}
|
||||||
|
rdkit_errors: {rdkit_errors}
|
||||||
|
"""
|
||||||
|
|
||||||
|
temp['detail'] = '\n'.join([x.strip() for x in detail.split('\n')])
|
||||||
|
# print(detail.strip())
|
||||||
|
|
||||||
|
results.append(temp)
|
||||||
|
|
||||||
|
res &= partial_res
|
||||||
|
|
||||||
|
results = sorted(results, key=lambda x: x['status'])
|
||||||
|
context['results'] = results
|
||||||
|
context['res'] = res
|
||||||
|
context['bt_rule_name'] = bt_rule_name
|
||||||
|
return render(request, 'migration_detail.html', context)
|
||||||
33
pyproject.toml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[project]
|
||||||
|
name = "envipy"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
dependencies = [
|
||||||
|
"celery>=5.5.2",
|
||||||
|
"django>=5.2.1",
|
||||||
|
"django-extensions>=4.1",
|
||||||
|
"django-model-utils>=5.0.0",
|
||||||
|
"django-ninja>=1.4.1",
|
||||||
|
"django-oauth-toolkit>=3.0.1",
|
||||||
|
"django-polymorphic>=4.1.0",
|
||||||
|
"enviformer",
|
||||||
|
"envipy-additional-information",
|
||||||
|
"envipy-plugins",
|
||||||
|
"epam-indigo>=1.30.1",
|
||||||
|
"gunicorn>=23.0.0",
|
||||||
|
"psycopg2-binary>=2.9.10",
|
||||||
|
"python-dotenv>=1.1.0",
|
||||||
|
"rdkit>=2025.3.2",
|
||||||
|
"redis>=6.1.0",
|
||||||
|
"requests>=2.32.3",
|
||||||
|
"scikit-learn>=1.6.1",
|
||||||
|
"sentry-sdk[django]>=2.32.0",
|
||||||
|
"setuptools>=80.8.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.0" }
|
||||||
|
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
|
||||||
|
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.4"}
|
||||||
168
static/css/c3.css
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/*-- Chart --*/
|
||||||
|
.c3 svg {
|
||||||
|
font: 10px sans-serif;
|
||||||
|
-webkit-tap-highlight-color: transparent; }
|
||||||
|
|
||||||
|
.c3 path, .c3 line {
|
||||||
|
fill: none;
|
||||||
|
stroke: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c3 text {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none; }
|
||||||
|
|
||||||
|
.c3-legend-item-tile,
|
||||||
|
.c3-xgrid-focus,
|
||||||
|
.c3-ygrid,
|
||||||
|
.c3-event-rect,
|
||||||
|
.c3-bars path {
|
||||||
|
shape-rendering: crispEdges; }
|
||||||
|
|
||||||
|
.c3-chart-arc path {
|
||||||
|
stroke: #fff; }
|
||||||
|
|
||||||
|
.c3-chart-arc text {
|
||||||
|
fill: #fff;
|
||||||
|
font-size: 13px; }
|
||||||
|
|
||||||
|
/*-- Axis --*/
|
||||||
|
/*-- Grid --*/
|
||||||
|
.c3-grid line {
|
||||||
|
stroke: #aaa; }
|
||||||
|
|
||||||
|
.c3-grid text {
|
||||||
|
fill: #aaa; }
|
||||||
|
|
||||||
|
.c3-xgrid, .c3-ygrid {
|
||||||
|
stroke-dasharray: 3 3; }
|
||||||
|
|
||||||
|
/*-- Text on Chart --*/
|
||||||
|
.c3-text.c3-empty {
|
||||||
|
fill: #808080;
|
||||||
|
font-size: 2em; }
|
||||||
|
|
||||||
|
/*-- Line --*/
|
||||||
|
.c3-line {
|
||||||
|
stroke-width: 1px; }
|
||||||
|
|
||||||
|
/*-- Point --*/
|
||||||
|
.c3-circle._expanded_ {
|
||||||
|
stroke-width: 1px;
|
||||||
|
stroke: white; }
|
||||||
|
|
||||||
|
.c3-selected-circle {
|
||||||
|
fill: white;
|
||||||
|
stroke-width: 2px; }
|
||||||
|
|
||||||
|
/*-- Bar --*/
|
||||||
|
.c3-bar {
|
||||||
|
stroke-width: 0; }
|
||||||
|
|
||||||
|
.c3-bar._expanded_ {
|
||||||
|
fill-opacity: 0.75; }
|
||||||
|
|
||||||
|
/*-- Focus --*/
|
||||||
|
.c3-target.c3-focused {
|
||||||
|
opacity: 1; }
|
||||||
|
|
||||||
|
.c3-target.c3-focused path.c3-line, .c3-target.c3-focused path.c3-step {
|
||||||
|
stroke-width: 2px; }
|
||||||
|
|
||||||
|
.c3-target.c3-defocused {
|
||||||
|
opacity: 0.3 !important; }
|
||||||
|
|
||||||
|
/*-- Region --*/
|
||||||
|
.c3-region {
|
||||||
|
fill: steelblue;
|
||||||
|
fill-opacity: .1; }
|
||||||
|
|
||||||
|
/*-- Brush --*/
|
||||||
|
.c3-brush .extent {
|
||||||
|
fill-opacity: .1; }
|
||||||
|
|
||||||
|
/*-- Select - Drag --*/
|
||||||
|
/*-- Legend --*/
|
||||||
|
.c3-legend-item {
|
||||||
|
font-size: 12px; }
|
||||||
|
|
||||||
|
.c3-legend-item-hidden {
|
||||||
|
opacity: 0.15; }
|
||||||
|
|
||||||
|
.c3-legend-background {
|
||||||
|
opacity: 0.75;
|
||||||
|
fill: white;
|
||||||
|
stroke: lightgray;
|
||||||
|
stroke-width: 1; }
|
||||||
|
|
||||||
|
/*-- Title --*/
|
||||||
|
.c3-title {
|
||||||
|
font: 14px sans-serif; }
|
||||||
|
|
||||||
|
/*-- Tooltip --*/
|
||||||
|
.c3-tooltip-container {
|
||||||
|
z-index: 10; }
|
||||||
|
|
||||||
|
.c3-tooltip {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
empty-cells: show;
|
||||||
|
-webkit-box-shadow: 7px 7px 12px -9px #777777;
|
||||||
|
-moz-box-shadow: 7px 7px 12px -9px #777777;
|
||||||
|
box-shadow: 7px 7px 12px -9px #777777;
|
||||||
|
opacity: 0.9; }
|
||||||
|
|
||||||
|
.c3-tooltip tr {
|
||||||
|
border: 1px solid #CCC; }
|
||||||
|
|
||||||
|
.c3-tooltip th {
|
||||||
|
background-color: #aaa;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
text-align: left;
|
||||||
|
color: #FFF; }
|
||||||
|
|
||||||
|
.c3-tooltip td {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-left: 1px dotted #999; }
|
||||||
|
|
||||||
|
.c3-tooltip td > span {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
margin-right: 6px; }
|
||||||
|
|
||||||
|
.c3-tooltip td.value {
|
||||||
|
text-align: right; }
|
||||||
|
|
||||||
|
/*-- Area --*/
|
||||||
|
.c3-area {
|
||||||
|
stroke-width: 0;
|
||||||
|
opacity: 0.2; }
|
||||||
|
|
||||||
|
/*-- Arc --*/
|
||||||
|
.c3-chart-arcs-title {
|
||||||
|
dominant-baseline: middle;
|
||||||
|
font-size: 1.3em; }
|
||||||
|
|
||||||
|
.c3-chart-arcs .c3-chart-arcs-background {
|
||||||
|
fill: #e0e0e0;
|
||||||
|
stroke: none; }
|
||||||
|
|
||||||
|
.c3-chart-arcs .c3-chart-arcs-gauge-unit {
|
||||||
|
fill: #000;
|
||||||
|
font-size: 16px; }
|
||||||
|
|
||||||
|
.c3-chart-arcs .c3-chart-arcs-gauge-max {
|
||||||
|
fill: #777; }
|
||||||
|
|
||||||
|
.c3-chart-arcs .c3-chart-arcs-gauge-min {
|
||||||
|
fill: #777; }
|
||||||
|
|
||||||
|
.c3-chart-arc .c3-gauge-value {
|
||||||
|
fill: #000;
|
||||||
|
/* font-size: 28px !important;*/ }
|
||||||
126
static/css/epp.css
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
.mini-submenu{
|
||||||
|
display:none;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.9);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 9px;
|
||||||
|
/*position: relative;*/
|
||||||
|
width: 42px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-submenu:hover{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-submenu .icon-bar {
|
||||||
|
border-radius: 1px;
|
||||||
|
display: block;
|
||||||
|
height: 2px;
|
||||||
|
width: 22px;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-submenu .icon-bar {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#slide-submenu{
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eductsdiv {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
#agentsdiv {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
#productsdiv {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
#actions {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.fade .modal-dialog {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog-multiple {
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
width: 600px;
|
||||||
|
margin: 30px auto;
|
||||||
|
max-height: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-element{
|
||||||
|
padding-top: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog-pps{
|
||||||
|
position: relative;
|
||||||
|
display: table;
|
||||||
|
margin-left: 35%;
|
||||||
|
margin-right: 35%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog-pps-big{
|
||||||
|
position: relative;
|
||||||
|
display: table;
|
||||||
|
margin-left: 25%;
|
||||||
|
margin-right: 25%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1220px) {
|
||||||
|
.navbar-header-framework {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
.navbar-left-framework,.navbar-right-framework {
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
.navbar-toggle-framework {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.navbar-collapse-framework {
|
||||||
|
border-top: 1px solid transparent;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.navbar-fixed-top-framework {
|
||||||
|
top: 0;
|
||||||
|
border-width: 0 0 1px;
|
||||||
|
}
|
||||||
|
.navbar-collapse-framework.collapse {
|
||||||
|
display: none!important;
|
||||||
|
}
|
||||||
|
.navbar-nav-framework {
|
||||||
|
float: none!important;
|
||||||
|
margin-top: 7.5px;
|
||||||
|
}
|
||||||
|
.navbar-nav-framework>li {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
.navbar-nav-framework>li>a {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.collapse-framework.in{
|
||||||
|
display:block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
static/images/ealogo.gif
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
static/images/enviPy-screenshot.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
static/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 192 KiB |
225
static/images/logo-long.svg
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="314.98749"
|
||||||
|
height="28.8125"
|
||||||
|
id="svg3004"
|
||||||
|
xml:space="preserve"><metadata
|
||||||
|
id="metadata3010"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs3008" /><g
|
||||||
|
transform="matrix(1.25,0,0,-1.25,0,28.8125)"
|
||||||
|
id="g3012"><g
|
||||||
|
transform="scale(0.1,0.1)"
|
||||||
|
id="g3014"><path
|
||||||
|
d="m 957.473,175.816 0,-4.296 -18.453,0 0,-48.614 -5.04,0 0,48.614 -18.378,0 0,4.296 41.871,0"
|
||||||
|
id="path3016"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 969.695,175.816 0,-22.968 31.425,0 0,22.968 5.04,0 0,-52.91 -5.04,0 0,25.637 -31.425,0 0,-25.637 -5.039,0 0,52.91 5.039,0"
|
||||||
|
id="path3018"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1055.58,175.816 0,-4.296 -31.49,0 0,-19.122 29.49,0 0,-4.293 -29.49,0 0,-20.898 31.87,0 0,-4.301 -36.91,0 0,52.91 36.53,0"
|
||||||
|
id="path3020"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1124.58,175.816 0,-4.296 -31.5,0 0,-19.122 29.49,0 0,-4.293 -29.49,0 0,-20.898 31.87,0 0,-4.301 -36.91,0 0,52.91 36.54,0"
|
||||||
|
id="path3022"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1139.76,175.816 30.83,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.64,0 -30.82,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
|
||||||
|
id="path3024"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1188.15,175.816 17.19,-47.355 0.15,0 17.06,47.355 5.34,0 -19.65,-52.91 -5.86,0 -19.56,52.91 5.33,0"
|
||||||
|
id="path3026"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1235.15,122.906 5.043,0 0,52.9102 -5.043,0 0,-52.9102 z"
|
||||||
|
id="path3028"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1277.3,150.691 c 1.54,0 2.99,0.243 4.38,0.711 1.39,0.469 2.59,1.145 3.63,2.036 1.04,0.886 1.86,1.968 2.48,3.222 0.63,1.258 0.92,2.703 0.92,4.336 0,3.262 -0.94,5.824 -2.81,7.703 -1.88,1.875 -4.74,2.821 -8.6,2.821 l -18.82,0 0,-20.829 18.82,0 z m 0.38,25.125 c 2.16,0 4.23,-0.269 6.19,-0.816 1.95,-0.543 3.65,-1.367 5.1,-2.48 1.46,-1.118 2.62,-2.54 3.49,-4.297 0.86,-1.758 1.29,-3.821 1.29,-6.192 0,-3.359 -0.86,-6.273 -2.6,-8.742 -1.72,-2.473 -4.29,-4.055 -7.69,-4.746 l 0,-0.145 c 1.73,-0.25 3.16,-0.703 4.29,-1.367 1.14,-0.668 2.06,-1.527 2.78,-2.558 0.72,-1.035 1.23,-2.239 1.56,-3.594 0.31,-1.363 0.53,-2.832 0.62,-4.41 0.05,-0.887 0.1,-1.977 0.16,-3.262 0.05,-1.285 0.15,-2.582 0.29,-3.891 0.15,-1.312 0.38,-2.543 0.71,-3.711 0.32,-1.156 0.74,-2.054 1.3,-2.699 l -5.56,0 c -0.29,0.496 -0.54,1.098 -0.7,1.809 -0.18,0.723 -0.31,1.461 -0.37,2.234 -0.08,0.762 -0.14,1.512 -0.2,2.254 -0.05,0.742 -0.1,1.387 -0.14,1.926 -0.09,1.875 -0.26,3.742 -0.49,5.598 -0.22,1.851 -0.69,3.503 -1.4,4.961 -0.72,1.46 -1.76,2.632 -3.11,3.523 -1.36,0.887 -3.23,1.281 -5.6,1.191 l -19.12,0 0,-23.496 -5.03,0 0,52.91 24.23,0"
|
||||||
|
id="path3030"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1308.46,140.879 c 0.77,-2.793 1.95,-5.289 3.56,-7.484 1.61,-2.2 3.66,-3.965 6.19,-5.301 2.52,-1.336 5.54,-2.004 9.04,-2.004 3.51,0 6.51,0.668 9,2.004 2.5,1.336 4.55,3.101 6.15,5.301 1.6,2.195 2.8,4.691 3.56,7.484 0.77,2.789 1.15,5.613 1.15,8.48 0,2.914 -0.38,5.758 -1.15,8.52 -0.76,2.769 -1.96,5.25 -3.56,7.449 -1.6,2.199 -3.65,3.969 -6.15,5.297 -2.49,1.336 -5.49,2.008 -9,2.008 -3.5,0 -6.52,-0.672 -9.04,-2.008 -2.53,-1.328 -4.58,-3.098 -6.19,-5.297 -1.61,-2.199 -2.79,-4.68 -3.56,-7.449 -0.75,-2.762 -1.15,-5.606 -1.15,-8.52 0,-2.867 0.4,-5.691 1.15,-8.48 z m -4.63,18.934 c 1.04,3.308 2.6,6.23 4.67,8.777 2.08,2.547 4.68,4.57 7.82,6.078 3.14,1.504 6.79,2.254 10.93,2.254 4.15,0 7.78,-0.75 10.89,-2.254 3.11,-1.508 5.71,-3.531 7.78,-6.078 2.08,-2.547 3.64,-5.469 4.67,-8.777 1.05,-3.313 1.56,-6.797 1.56,-10.454 0,-3.656 -0.51,-7.136 -1.56,-10.449 -1.03,-3.308 -2.59,-6.226 -4.67,-8.738 -2.07,-2.52 -4.67,-4.531 -7.78,-6.043 -3.11,-1.5 -6.74,-2.266 -10.89,-2.266 -4.14,0 -7.79,0.766 -10.93,2.266 -3.14,1.512 -5.74,3.523 -7.82,6.043 -2.07,2.512 -3.63,5.43 -4.67,8.738 -1.04,3.313 -1.54,6.793 -1.54,10.449 0,3.657 0.5,7.141 1.54,10.454"
|
||||||
|
id="path3032"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1367.77,175.816 30.84,-44.757 0.15,0 0,44.757 5.05,0 0,-52.91 -5.64,0 -30.83,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.62,0"
|
||||||
|
id="path3034"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1423.89,175.816 18.3,-46.386 18.22,46.386 7.41,0 0,-52.91 -5.03,0 0,45.723 -0.15,0 -18.09,-45.723 -4.74,0 -18.15,45.723 -0.15,0 0,-45.723 -5.04,0 0,52.91 7.42,0"
|
||||||
|
id="path3036"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1517.1,175.816 0,-4.296 -31.48,0 0,-19.122 29.48,0 0,-4.293 -29.48,0 0,-20.898 31.86,0 0,-4.301 -36.9,0 0,52.91 36.52,0"
|
||||||
|
id="path3038"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1532.29,175.816 30.82,-44.757 0.14,0 0,44.757 5.03,0 0,-52.91 -5.62,0 -30.81,44.754 -0.15,0 0,-44.754 -5.03,0 0,52.91 5.62,0"
|
||||||
|
id="path3040"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1617.34,175.816 0,-4.296 -18.44,0 0,-48.614 -5.04,0 0,48.614 -18.38,0 0,4.296 41.86,0"
|
||||||
|
id="path3042"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1647.8,143.656 -10.22,27.121 -10.6,-27.121 20.82,0 z m -7.18,32.16 20.74,-52.91 -5.4,0 -6.46,16.449 -24.07,0 -6.38,-16.449 -5.33,0 21.26,52.91 5.64,0"
|
||||||
|
id="path3044"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1673.44,175.816 0,-48.609 29.65,0 0,-4.301 -34.68,0 0,52.91 5.03,0"
|
||||||
|
id="path3046"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1769.74,165.254 c -1.02,1.605 -2.25,2.953 -3.71,4.043 -1.46,1.082 -3.06,1.914 -4.81,2.48 -1.76,0.567 -3.6,0.856 -5.52,0.856 -3.51,0 -6.53,-0.672 -9.05,-2.008 -2.52,-1.328 -4.58,-3.098 -6.18,-5.297 -1.61,-2.199 -2.8,-4.68 -3.56,-7.449 -0.77,-2.762 -1.15,-5.606 -1.15,-8.52 0,-2.867 0.38,-5.691 1.15,-8.48 0.76,-2.793 1.95,-5.289 3.56,-7.484 1.6,-2.2 3.66,-3.965 6.18,-5.301 2.52,-1.336 5.54,-2.004 9.05,-2.004 2.46,0 4.69,0.449 6.66,1.336 1.98,0.89 3.68,2.101 5.12,3.633 1.43,1.523 2.59,3.324 3.48,5.371 0.89,2.05 1.45,4.261 1.71,6.633 l 5.03,0 c -0.35,-3.258 -1.11,-6.204 -2.3,-8.821 -1.18,-2.617 -2.7,-4.84 -4.59,-6.664 -1.88,-1.824 -4.08,-3.238 -6.63,-4.223 -2.54,-0.992 -5.37,-1.492 -8.48,-1.492 -4.17,0 -7.81,0.766 -10.93,2.266 -3.15,1.512 -5.76,3.523 -7.83,6.043 -2.07,2.512 -3.62,5.43 -4.65,8.738 -1.04,3.313 -1.57,6.793 -1.57,10.449 0,3.657 0.53,7.141 1.57,10.454 1.03,3.308 2.58,6.23 4.65,8.777 2.07,2.547 4.68,4.57 7.83,6.078 3.12,1.504 6.76,2.254 10.93,2.254 2.52,0 4.97,-0.371 7.38,-1.106 2.39,-0.738 4.56,-1.843 6.51,-3.296 1.95,-1.461 3.58,-3.254 4.89,-5.372 1.3,-2.125 2.13,-4.574 2.47,-7.335 l -5.04,0 c -0.43,2.019 -1.16,3.839 -2.17,5.441"
|
||||||
|
id="path3048"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1791.01,140.879 c 0.76,-2.793 1.95,-5.289 3.56,-7.484 1.6,-2.2 3.66,-3.965 6.18,-5.301 2.52,-1.336 5.53,-2.004 9.04,-2.004 3.51,0 6.5,0.668 9.01,2.004 2.49,1.336 4.54,3.101 6.14,5.301 1.61,2.195 2.79,4.691 3.57,7.484 0.75,2.789 1.14,5.613 1.14,8.48 0,2.914 -0.39,5.758 -1.14,8.52 -0.78,2.769 -1.96,5.25 -3.57,7.449 -1.6,2.199 -3.65,3.969 -6.14,5.297 -2.51,1.336 -5.5,2.008 -9.01,2.008 -3.51,0 -6.52,-0.672 -9.04,-2.008 -2.52,-1.328 -4.58,-3.098 -6.18,-5.297 -1.61,-2.199 -2.8,-4.68 -3.56,-7.449 -0.78,-2.762 -1.16,-5.606 -1.16,-8.52 0,-2.867 0.38,-5.691 1.16,-8.48 z m -4.64,18.934 c 1.04,3.308 2.6,6.23 4.67,8.777 2.08,2.547 4.68,4.57 7.82,6.078 3.13,1.504 6.77,2.254 10.93,2.254 4.15,0 7.78,-0.75 10.89,-2.254 3.11,-1.508 5.72,-3.531 7.79,-6.078 2.07,-2.547 3.62,-5.469 4.66,-8.777 1.04,-3.313 1.56,-6.797 1.56,-10.454 0,-3.656 -0.52,-7.136 -1.56,-10.449 -1.04,-3.308 -2.59,-6.226 -4.66,-8.738 -2.07,-2.52 -4.68,-4.531 -7.79,-6.043 -3.11,-1.5 -6.74,-2.266 -10.89,-2.266 -4.16,0 -7.8,0.766 -10.93,2.266 -3.14,1.512 -5.74,3.523 -7.82,6.043 -2.07,2.512 -3.63,5.43 -4.67,8.738 -1.04,3.313 -1.56,6.793 -1.56,10.449 0,3.657 0.52,7.141 1.56,10.454"
|
||||||
|
id="path3050"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1850.33,175.816 30.82,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.64,0 -30.82,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.64,0"
|
||||||
|
id="path3052"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1935.39,175.816 0,-4.296 -18.45,0 0,-48.614 -5.04,0 0,48.614 -18.37,0 0,4.296 41.86,0"
|
||||||
|
id="path3054"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1965.86,143.656 -10.23,27.121 -10.6,-27.121 20.83,0 z m -7.21,32.16 20.76,-52.91 -5.41,0 -6.44,16.449 -24.08,0 -6.38,-16.449 -5.33,0 21.26,52.91 5.62,0"
|
||||||
|
id="path3056"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1993.71,175.816 18.3,-46.386 18.24,46.386 7.41,0 0,-52.91 -5.04,0 0,45.723 -0.15,0 -18.09,-45.723 -4.73,0 -18.16,45.723 -0.14,0 0,-45.723 -5.05,0 0,52.91 7.41,0"
|
||||||
|
id="path3058"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2050.78,122.906 5.0273,0 0,52.9102 -5.0273,0 0,-52.9102 z"
|
||||||
|
id="path3060"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2074.63,175.816 30.83,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.63,0 -30.83,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
|
||||||
|
id="path3062"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2151.78,143.656 -10.24,27.121 -10.58,-27.121 20.82,0 z m -7.2,32.16 20.76,-52.91 -5.41,0 -6.45,16.449 -24.09,0 -6.36,-16.449 -5.34,0 21.27,52.91 5.62,0"
|
||||||
|
id="path3064"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2177.93,175.816 30.82,-44.757 0.16,0 0,44.757 5.05,0 0,-52.91 -5.64,0 -30.84,44.754 -0.14,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
|
||||||
|
id="path3066"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2263.01,175.816 0,-4.296 -18.46,0 0,-48.614 -5.04,0 0,48.614 -18.38,0 0,4.296 41.88,0"
|
||||||
|
id="path3068"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 945.785,32.6602 c 1.875,0 3.656,0.1562 5.336,0.4804 1.676,0.3164 3.16,0.8985 4.445,1.7383 1.286,0.8477 2.301,1.9649 3.043,3.375 0.739,1.4102 1.11,3.1719 1.11,5.2969 0,3.4101 -1.203,5.9648 -3.602,7.668 -2.387,1.7031 -5.836,2.5585 -10.332,2.5585 l -17.344,0 0,-21.1171 17.344,0 z m 0,25.414 c 2.028,0 3.778,0.2344 5.262,0.711 1.48,0.4609 2.719,1.1015 3.711,1.9218 0.98,0.8125 1.726,1.7696 2.215,2.8555 0.5,1.082 0.742,2.2422 0.742,3.4766 0,6.6211 -3.977,9.9375 -11.93,9.9375 l -17.344,0 0,-18.9024 17.344,0 z m 0,23.1953 c 2.223,0 4.36,-0.2109 6.41,-0.6289 2.051,-0.4218 3.852,-1.1328 5.41,-2.1484 1.559,-1.0156 2.805,-2.3438 3.743,-4 0.937,-1.6563 1.406,-3.7227 1.406,-6.1914 0,-1.3867 -0.219,-2.7305 -0.668,-4.0391 -0.445,-1.3086 -1.074,-2.4961 -1.887,-3.5547 -0.808,-1.0625 -1.781,-1.9687 -2.89,-2.7031 -1.114,-0.7422 -2.36,-1.2617 -3.739,-1.5586 l 0,-0.1523 c 3.403,-0.4453 6.121,-1.8321 8.145,-4.1797 2.031,-2.3477 3.043,-5.25 3.043,-8.711 0,-0.8398 -0.074,-1.7851 -0.227,-2.8515 -0.144,-1.0625 -0.437,-2.1524 -0.886,-3.2617 -0.446,-1.1133 -1.086,-2.2149 -1.93,-3.2969 -0.836,-1.0859 -1.957,-2.0391 -3.363,-2.8516 -1.414,-0.8203 -3.141,-1.4883 -5.192,-2.0039 -2.051,-0.5195 -4.508,-0.7812 -7.375,-0.7812 l -22.379,0 0,52.914 22.379,0"
|
||||||
|
id="path3070"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 975.426,28.3555 5.04297,0 0,52.9141 -5.04297,0 0,-52.9141 z"
|
||||||
|
id="path3072"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 997.105,46.3281 c 0.762,-2.789 1.95,-5.2812 3.555,-7.4804 1.6,-2.1993 3.67,-3.9688 6.18,-5.2969 2.53,-1.3399 5.54,-2.0039 9.04,-2.0039 3.51,0 6.52,0.664 9.01,2.0039 2.5,1.3281 4.54,3.0976 6.15,5.2969 1.61,2.1992 2.79,4.6914 3.55,7.4804 0.77,2.793 1.16,5.6211 1.16,8.4844 0,2.918 -0.39,5.7578 -1.16,8.5273 -0.76,2.7618 -1.94,5.2461 -3.55,7.4454 -1.61,2.2031 -3.65,3.9648 -6.15,5.3007 -2.49,1.3321 -5.5,2 -9.01,2 -3.5,0 -6.51,-0.6679 -9.04,-2 -2.51,-1.3359 -4.58,-3.0976 -6.18,-5.3007 -1.605,-2.1993 -2.793,-4.6836 -3.555,-7.4454 -0.773,-2.7695 -1.152,-5.6093 -1.152,-8.5273 0,-2.8633 0.379,-5.6914 1.152,-8.4844 z m -4.632,18.9336 c 1.035,3.3125 2.59,6.2383 4.668,8.7813 2.074,2.5429 4.679,4.5742 7.819,6.0781 3.13,1.5078 6.77,2.2578 10.92,2.2578 4.15,0 7.79,-0.75 10.9,-2.2578 3.11,-1.5039 5.71,-3.5352 7.78,-6.0781 2.08,-2.543 3.63,-5.4688 4.67,-8.7813 1.04,-3.3047 1.56,-6.789 1.56,-10.4492 0,-3.6602 -0.52,-7.1406 -1.56,-10.4414 -1.04,-3.3164 -2.59,-6.2305 -4.67,-8.7461 -2.07,-2.5195 -4.67,-4.5312 -7.78,-6.0391 -3.11,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.15,0 -7.79,0.7617 -10.92,2.2656 -3.14,1.5079 -5.745,3.5196 -7.819,6.0391 -2.078,2.5156 -3.633,5.4297 -4.668,8.7461 -1.035,3.3008 -1.559,6.7812 -1.559,10.4414 0,3.6602 0.524,7.1445 1.559,10.4492"
|
||||||
|
id="path3074"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1087.1,81.2695 0,-4.2929 -18.45,0 0,-48.6211 -5.04,0 0,48.6211 -18.38,0 0,4.2929 41.87,0"
|
||||||
|
id="path3076"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1118.15,56.1484 c 1.53,0 2.99,0.2383 4.37,0.7071 1.39,0.4687 2.6,1.1484 3.63,2.0351 1.04,0.8907 1.86,1.9688 2.49,3.2227 0.61,1.2617 0.93,2.7109 0.93,4.332 0,3.2656 -0.95,5.836 -2.82,7.7031 -1.88,1.8829 -4.75,2.8282 -8.6,2.8282 l -18.82,0 0,-20.8282 18.82,0 z m 0.37,25.1211 c 2.17,0 4.23,-0.2695 6.19,-0.8203 1.95,-0.539 3.65,-1.3633 5.11,-2.4765 1.46,-1.1133 2.62,-2.543 3.49,-4.3008 0.86,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.86,-6.2696 -2.59,-8.7461 -1.73,-2.4688 -4.3,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.73,-0.25 3.16,-0.7032 4.3,-1.3672 1.14,-0.668 2.06,-1.5235 2.78,-2.5586 0.71,-1.0391 1.23,-2.2344 1.55,-3.5977 0.33,-1.3593 0.54,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.3,-3.8868 0.15,-1.3125 0.38,-2.5468 0.71,-3.707 0.31,-1.1562 0.74,-2.0586 1.29,-2.707 l -5.56,0 c -0.29,0.5 -0.53,1.1015 -0.7,1.8203 -0.17,0.7187 -0.3,1.4531 -0.37,2.2265 -0.07,0.7618 -0.14,1.5118 -0.19,2.25 -0.04,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.26,3.7461 -0.48,5.6016 -0.22,1.8555 -0.69,3.5 -1.41,4.9609 -0.71,1.4532 -1.75,2.6289 -3.11,3.5235 -1.36,0.8906 -3.22,1.2812 -5.59,1.1875 l -19.12,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
|
||||||
|
id="path3078"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1174.25,49.1055 -10.23,27.125 -10.6,-27.125 20.83,0 z m -7.19,32.164 20.75,-52.914 -5.41,0 -6.45,16.457 -24.08,0 -6.38,-16.457 -5.33,0 21.27,52.914 5.63,0"
|
||||||
|
id="path3080"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1200.41,81.2695 30.83,-44.7617 0.15,0 0,44.7617 5.04,0 0,-52.914 -5.63,0 -30.84,44.7578 -0.15,0 0,-44.7578 -5.04,0 0,52.914 5.64,0"
|
||||||
|
id="path3082"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1252.87,38.9609 c 0.9,-1.8281 2.12,-3.289 3.67,-4.375 1.57,-1.0898 3.4,-1.8671 5.52,-2.3359 2.12,-0.4727 4.4,-0.7031 6.83,-0.7031 1.37,0 2.89,0.1914 4.52,0.5976 1.62,0.3907 3.14,1.0196 4.55,1.8828 1.42,0.8711 2.58,1.9727 3.52,3.336 0.94,1.3515 1.41,3.0039 1.41,4.9258 0,1.4843 -0.33,2.7695 -1.01,3.8593 -0.66,1.086 -1.53,1.9961 -2.58,2.7383 -1.07,0.7422 -2.24,1.3438 -3.53,1.8164 -1.28,0.4688 -2.55,0.8555 -3.78,1.1524 l -11.78,2.8789 c -1.54,0.4062 -3.02,0.8906 -4.49,1.4922 -1.44,0.5898 -2.72,1.375 -3.81,2.3672 -1.09,0.9843 -1.95,2.2031 -2.63,3.6289 -0.67,1.4336 -1,3.1875 -1,5.2617 0,1.289 0.25,2.7929 0.74,4.5234 0.49,1.7266 1.42,3.3594 2.79,4.8906 1.35,1.5274 3.21,2.8243 5.59,3.8907 2.37,1.0625 5.4,1.5898 9.1,1.5898 2.62,0 5.13,-0.3398 7.49,-1.0351 2.38,-0.6915 4.47,-1.7305 6.22,-3.1133 1.8,-1.3789 3.21,-3.0977 4.26,-5.1446 1.08,-2.0546 1.6,-4.4453 1.6,-7.1601 l -5.03,0 c -0.1,2.0351 -0.56,3.7969 -1.37,5.3047 -0.82,1.5039 -1.88,2.7617 -3.19,3.7773 -1.3,1.0117 -2.81,1.7852 -4.53,2.3008 -1.7,0.5195 -3.49,0.7773 -5.37,0.7773 -1.72,0 -3.39,-0.1914 -5,-0.5586 -1.61,-0.371 -3.01,-0.9609 -4.22,-1.7812 -1.21,-0.8164 -2.18,-1.8906 -2.93,-3.2227 -0.74,-1.332 -1.11,-2.9882 -1.11,-4.9648 0,-1.2305 0.22,-2.3086 0.64,-3.2227 0.42,-0.914 0.98,-1.6953 1.72,-2.332 0.75,-0.6484 1.61,-1.1601 2.56,-1.5547 0.98,-0.4023 1.99,-0.7226 3.08,-0.9687 l 12.9,-3.1875 c 1.88,-0.4883 3.64,-1.0938 5.3,-1.8164 1.65,-0.711 3.11,-1.6055 4.37,-2.6602 1.27,-1.0625 2.24,-2.3633 2.97,-3.8945 0.71,-1.5313 1.07,-3.3867 1.07,-5.5586 0,-0.5899 -0.07,-1.3828 -0.2,-2.3672 -0.11,-0.9922 -0.41,-2.0313 -0.87,-3.1523 -0.47,-1.1133 -1.14,-2.2344 -2,-3.3711 -0.87,-1.1368 -2.06,-2.1602 -3.56,-3.0782 -1.51,-0.9062 -3.37,-1.6562 -5.6,-2.2226 -2.22,-0.5586 -4.88,-0.8516 -8,-0.8516 -3.11,0 -6,0.3594 -8.68,1.0781 -2.65,0.7188 -4.94,1.8125 -6.81,3.2969 -1.88,1.4844 -3.32,3.3789 -4.34,5.707 -1,2.3204 -1.43,5.1055 -1.3,8.375 l 5.05,0 c -0.06,-2.7148 0.37,-4.9921 1.25,-6.8164"
|
||||||
|
id="path3084"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1331.72,81.2695 0,-4.2929 -28.53,0 0,-19.1211 25.35,0 0,-4.3008 -25.35,0 0,-25.1992 -5.04,0 0,52.914 33.57,0"
|
||||||
|
id="path3086"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1343.54,46.3281 c 0.78,-2.789 1.95,-5.2812 3.55,-7.4804 1.6,-2.1993 3.68,-3.9688 6.2,-5.2969 2.51,-1.3399 5.53,-2.0039 9.03,-2.0039 3.51,0 6.51,0.664 9.01,2.0039 2.5,1.3281 4.55,3.0976 6.15,5.2969 1.6,2.1992 2.78,4.6914 3.56,7.4804 0.76,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.39,5.7578 -1.15,8.5273 -0.78,2.7618 -1.96,5.2461 -3.56,7.4454 -1.6,2.2031 -3.65,3.9648 -6.15,5.3007 -2.5,1.3321 -5.5,2 -9.01,2 -3.5,0 -6.52,-0.6679 -9.03,-2 -2.52,-1.3359 -4.6,-3.0976 -6.2,-5.3007 -1.6,-2.1993 -2.77,-4.6836 -3.55,-7.4454 -0.77,-2.7695 -1.14,-5.6093 -1.14,-8.5273 0,-2.8633 0.37,-5.6914 1.14,-8.4844 z m -4.63,18.9336 c 1.03,3.3125 2.59,6.2383 4.66,8.7813 2.09,2.5429 4.69,4.5742 7.82,6.0781 3.14,1.5078 6.79,2.2578 10.93,2.2578 4.15,0 7.8,-0.75 10.9,-2.2578 3.11,-1.5039 5.7,-3.5352 7.78,-6.0781 2.09,-2.543 3.63,-5.4688 4.66,-8.7813 1.04,-3.3047 1.57,-6.789 1.57,-10.4492 0,-3.6602 -0.53,-7.1406 -1.57,-10.4414 -1.03,-3.3164 -2.57,-6.2305 -4.66,-8.7461 -2.08,-2.5195 -4.67,-4.5312 -7.78,-6.0391 -3.1,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.14,0 -7.79,0.7617 -10.93,2.2656 -3.13,1.5079 -5.73,3.5196 -7.82,6.0391 -2.07,2.5156 -3.63,5.4297 -4.66,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492"
|
||||||
|
id="path3088"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1421.16,56.1484 c 1.54,0 3,0.2383 4.39,0.7071 1.37,0.4687 2.58,1.1484 3.62,2.0351 1.04,0.8907 1.87,1.9688 2.49,3.2227 0.61,1.2617 0.91,2.7109 0.91,4.332 0,3.2656 -0.93,5.836 -2.81,7.7031 -1.88,1.8829 -4.73,2.8282 -8.6,2.8282 l -18.83,0 0,-20.8282 18.83,0 z m 0.37,25.1211 c 2.18,0 4.24,-0.2695 6.18,-0.8203 1.96,-0.539 3.67,-1.3633 5.12,-2.4765 1.47,-1.1133 2.63,-2.543 3.49,-4.3008 0.86,-1.7578 1.3,-3.8125 1.3,-6.1875 0,-3.3594 -0.87,-6.2696 -2.6,-8.7461 -1.72,-2.4688 -4.3,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.74,-0.25 3.17,-0.7032 4.3,-1.3672 1.13,-0.668 2.06,-1.5235 2.78,-2.5586 0.72,-1.0391 1.24,-2.2344 1.56,-3.5977 0.32,-1.3593 0.52,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.29,-3.8868 0.16,-1.3125 0.38,-2.5468 0.7,-3.707 0.33,-1.1562 0.76,-2.0586 1.3,-2.707 l -5.55,0 c -0.31,0.5 -0.54,1.1015 -0.71,1.8203 -0.17,0.7187 -0.29,1.4531 -0.37,2.2265 -0.08,0.7618 -0.13,1.5118 -0.18,2.25 -0.05,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.25,3.7461 -0.48,5.6016 -0.22,1.8555 -0.7,3.5 -1.41,4.9609 -0.73,1.4532 -1.76,2.6289 -3.12,3.5235 -1.36,0.8906 -3.21,1.2812 -5.59,1.1875 l -19.13,0 0,-23.5 -5.03,0 0,52.914 24.23,0"
|
||||||
|
id="path3090"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1456.21,81.2695 18.3,-46.3906 18.24,46.3906 7.41,0 0,-52.914 -5.04,0 0,45.7265 -0.15,0 -18.08,-45.7265 -4.74,0 -18.17,45.7265 -0.13,0 0,-45.7265 -5.05,0 0,52.914 7.41,0"
|
||||||
|
id="path3092"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1541.21,49.1055 -10.22,27.125 -10.6,-27.125 20.82,0 z m -7.19,32.164 20.74,-52.914 -5.42,0 -6.42,16.457 -24.08,0 -6.38,-16.457 -5.33,0 21.27,52.914 5.62,0"
|
||||||
|
id="path3094"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1593,81.2695 0,-4.2929 -18.47,0 0,-48.6211 -5.04,0 0,48.6211 -18.37,0 0,4.2929 41.88,0"
|
||||||
|
id="path3096"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1600.55,28.3555 5.0391,0 0,52.9141 -5.0391,0 0,-52.9141 z"
|
||||||
|
id="path3098"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1622.23,46.3281 c 0.76,-2.789 1.94,-5.2812 3.55,-7.4804 1.6,-2.1993 3.67,-3.9688 6.19,-5.2969 2.52,-1.3399 5.53,-2.0039 9.04,-2.0039 3.5,0 6.5,0.664 9.01,2.0039 2.48,1.3281 4.54,3.0976 6.14,5.2969 1.61,2.1992 2.8,4.6914 3.56,7.4804 0.76,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.39,5.7578 -1.15,8.5273 -0.76,2.7618 -1.95,5.2461 -3.56,7.4454 -1.6,2.2031 -3.66,3.9648 -6.14,5.3007 -2.51,1.3321 -5.51,2 -9.01,2 -3.51,0 -6.52,-0.6679 -9.04,-2 -2.52,-1.3359 -4.59,-3.0976 -6.19,-5.3007 -1.61,-2.1993 -2.79,-4.6836 -3.55,-7.4454 -0.77,-2.7695 -1.16,-5.6093 -1.16,-8.5273 0,-2.8633 0.39,-5.6914 1.16,-8.4844 z m -4.64,18.9336 c 1.03,3.3125 2.6,6.2383 4.68,8.7813 2.07,2.5429 4.67,4.5742 7.8,6.0781 3.14,1.5078 6.79,2.2578 10.94,2.2578 4.16,0 7.78,-0.75 10.88,-2.2578 3.13,-1.5039 5.72,-3.5352 7.8,-6.0781 2.07,-2.543 3.62,-5.4688 4.66,-8.7813 1.03,-3.3047 1.55,-6.789 1.55,-10.4492 0,-3.6602 -0.52,-7.1406 -1.55,-10.4414 -1.04,-3.3164 -2.59,-6.2305 -4.66,-8.7461 -2.08,-2.5195 -4.67,-4.5312 -7.8,-6.0391 -3.1,-1.5039 -6.72,-2.2656 -10.88,-2.2656 -4.15,0 -7.8,0.7617 -10.94,2.2656 -3.13,1.5079 -5.73,3.5196 -7.8,6.0391 -2.08,2.5156 -3.65,5.4297 -4.68,8.7461 -1.03,3.3008 -1.55,6.7812 -1.55,10.4414 0,3.6602 0.52,7.1445 1.55,10.4492"
|
||||||
|
id="path3100"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1681.55,81.2695 30.82,-44.7617 0.15,0 0,44.7617 5.04,0 0,-52.914 -5.64,0 -30.82,44.7578 -0.15,0 0,-44.7578 -5.03,0 0,52.914 5.63,0"
|
||||||
|
id="path3102"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1775.58,55.332 c 3.51,0 6.35,0.8946 8.52,2.6719 2.17,1.7774 3.26,4.4922 3.26,8.1484 0,3.6602 -1.09,6.3711 -3.26,8.1485 -2.17,1.7851 -5.01,2.6758 -8.52,2.6758 l -17.35,0 0,-21.6446 17.35,0 z m 1.12,25.9375 c 2.36,0 4.51,-0.3359 6.43,-0.9961 1.94,-0.6679 3.58,-1.6562 4.99,-2.9648 1.37,-1.3125 2.43,-2.9063 3.17,-4.7852 0.74,-1.875 1.11,-4.0039 1.11,-6.3711 0,-2.3671 -0.37,-4.4921 -1.11,-6.371 -0.74,-1.8829 -1.8,-3.4766 -3.17,-4.7852 -1.41,-1.3047 -3.05,-2.293 -4.99,-2.9609 -1.92,-0.6641 -4.07,-0.9961 -6.43,-0.9961 l -18.47,0 0,-22.6836 -5.04,0 0,52.914 23.51,0"
|
||||||
|
id="path3104"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1824.93,49.1055 -10.22,27.125 -10.6,-27.125 20.82,0 z m -7.19,32.164 20.76,-52.914 -5.41,0 -6.45,16.457 -24.09,0 -6.38,-16.457 -5.33,0 21.28,52.914 5.62,0"
|
||||||
|
id="path3106"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1876.73,81.2695 0,-4.2929 -18.45,0 0,-48.6211 -5.04,0 0,48.6211 -18.38,0 0,4.2929 41.87,0"
|
||||||
|
id="path3108"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1888.96,81.2695 0,-22.9687 31.41,0 0,22.9687 5.04,0 0,-52.914 -5.04,0 0,25.6445 -31.41,0 0,-25.6445 -5.04,0 0,52.914 5.04,0"
|
||||||
|
id="path3110"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 1938.38,81.2695 12.01,-46.3125 0.15,0 12.9,46.3125 6.3,0 12.96,-46.3125 0.15,0 12.07,46.3125 5.04,0 -14.59,-52.914 -5.34,0 -13.42,47.3515 -0.14,0 -13.34,-47.3515 -5.48,0 -14.67,52.914 5.4,0"
|
||||||
|
id="path3112"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2034.73,49.1055 -10.24,27.125 -10.59,-27.125 20.83,0 z m -7.2,32.164 20.75,-52.914 -5.41,0 -6.44,16.457 -24.09,0 -6.37,-16.457 -5.34,0 21.27,52.914 5.63,0"
|
||||||
|
id="path3114"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2043.84,81.2695 5.93,0 17.41,-26.8281 17.33,26.8281 6.01,0 -20.9,-31.125 0,-21.789 -5.04,0 0,21.789 -20.74,31.125"
|
||||||
|
id="path3116"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2144.01,56.1484 c 1.55,0 3,0.2383 4.38,0.7071 1.39,0.4687 2.6,1.1484 3.63,2.0351 1.05,0.8907 1.88,1.9688 2.48,3.2227 0.62,1.2617 0.93,2.7109 0.93,4.332 0,3.2656 -0.94,5.836 -2.81,7.7031 -1.89,1.8829 -4.75,2.8282 -8.61,2.8282 l -18.81,0 0,-20.8282 18.81,0 z m 0.38,25.1211 c 2.18,0 4.23,-0.2695 6.19,-0.8203 1.95,-0.539 3.66,-1.3633 5.1,-2.4765 1.47,-1.1133 2.63,-2.543 3.5,-4.3008 0.86,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.86,-6.2696 -2.59,-8.7461 -1.73,-2.4688 -4.3,-4.0469 -7.7,-4.7383 l 0,-0.1484 c 1.72,-0.25 3.15,-0.7032 4.28,-1.3672 1.15,-0.668 2.07,-1.5235 2.79,-2.5586 0.72,-1.0391 1.24,-2.2344 1.55,-3.5977 0.32,-1.3593 0.54,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.29,-3.8868 0.15,-1.3125 0.39,-2.5468 0.71,-3.707 0.33,-1.1562 0.76,-2.0586 1.3,-2.707 l -5.55,0 c -0.3,0.5 -0.54,1.1015 -0.71,1.8203 -0.17,0.7187 -0.3,1.4531 -0.38,2.2265 -0.07,0.7618 -0.13,1.5118 -0.18,2.25 -0.04,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.26,3.7461 -0.49,5.6016 -0.22,1.8555 -0.68,3.5 -1.39,4.9609 -0.73,1.4532 -1.76,2.6289 -3.13,3.5235 -1.35,0.8906 -3.21,1.2812 -5.59,1.1875 l -19.11,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
|
||||||
|
id="path3118"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2208.34,81.2695 0,-4.2929 -31.49,0 0,-19.1211 29.49,0 0,-4.3008 -29.49,0 0,-20.8945 31.87,0 0,-4.3047 -36.91,0 0,52.914 36.53,0"
|
||||||
|
id="path3120"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2221.6,38.9609 c 0.9,-1.8281 2.13,-3.289 3.66,-4.375 1.58,-1.0898 3.4,-1.8671 5.53,-2.3359 2.12,-0.4727 4.41,-0.7031 6.82,-0.7031 1.38,0 2.9,0.1914 4.53,0.5976 1.62,0.3907 3.13,1.0196 4.55,1.8828 1.41,0.8711 2.58,1.9727 3.52,3.336 0.94,1.3515 1.4,3.0039 1.4,4.9258 0,1.4843 -0.32,2.7695 -0.99,3.8593 -0.66,1.086 -1.55,1.9961 -2.59,2.7383 -1.06,0.7422 -2.24,1.3438 -3.53,1.8164 -1.28,0.4688 -2.54,0.8555 -3.78,1.1524 l -11.77,2.8789 c -1.55,0.4062 -3.03,0.8906 -4.5,1.4922 -1.45,0.5898 -2.73,1.375 -3.82,2.3672 -1.08,0.9843 -1.95,2.2031 -2.62,3.6289 -0.66,1.4336 -1,3.1875 -1,5.2617 0,1.289 0.24,2.7929 0.74,4.5234 0.5,1.7266 1.42,3.3594 2.78,4.8906 1.36,1.5274 3.23,2.8243 5.59,3.8907 2.38,1.0625 5.41,1.5898 9.12,1.5898 2.61,0 5.11,-0.3398 7.48,-1.0351 2.37,-0.6915 4.46,-1.7305 6.24,-3.1133 1.77,-1.3789 3.19,-3.0977 4.24,-5.1446 1.08,-2.0546 1.6,-4.4453 1.6,-7.1601 l -5.03,0 c -0.1,2.0351 -0.55,3.7969 -1.38,5.3047 -0.81,1.5039 -1.87,2.7617 -3.18,3.7773 -1.31,1.0117 -2.82,1.7852 -4.53,2.3008 -1.71,0.5195 -3.48,0.7773 -5.37,0.7773 -1.73,0 -3.4,-0.1914 -5.01,-0.5586 -1.6,-0.371 -3,-0.9609 -4.21,-1.7812 -1.21,-0.8164 -2.19,-1.8906 -2.94,-3.2227 -0.74,-1.332 -1.1,-2.9882 -1.1,-4.9648 0,-1.2305 0.21,-2.3086 0.63,-3.2227 0.43,-0.914 0.99,-1.6953 1.73,-2.332 0.75,-0.6484 1.62,-1.1601 2.56,-1.5547 0.97,-0.4023 1.99,-0.7226 3.08,-0.9687 l 12.9,-3.1875 c 1.87,-0.4883 3.64,-1.0938 5.3,-1.8164 1.65,-0.711 3.11,-1.6055 4.37,-2.6602 1.26,-1.0625 2.24,-2.3633 2.97,-3.8945 0.71,-1.5313 1.07,-3.3867 1.07,-5.5586 0,-0.5899 -0.07,-1.3828 -0.2,-2.3672 -0.11,-0.9922 -0.41,-2.0313 -0.87,-3.1523 -0.48,-1.1133 -1.15,-2.2344 -2.01,-3.3711 -0.86,-1.1368 -2.05,-2.1602 -3.55,-3.0782 -1.5,-0.9062 -3.37,-1.6562 -5.6,-2.2226 -2.22,-0.5586 -4.89,-0.8516 -8.01,-0.8516 -3.11,0 -5.99,0.3594 -8.67,1.0781 -2.66,0.7188 -4.94,1.8125 -6.8,3.2969 -1.89,1.4844 -3.33,3.3789 -4.36,5.707 -0.99,2.3204 -1.43,5.1055 -1.29,8.375 l 5.04,0 c -0.05,-2.7148 0.38,-4.9921 1.26,-6.8164"
|
||||||
|
id="path3122"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2270.24,46.3281 c 0.79,-2.789 1.96,-5.2812 3.57,-7.4804 1.6,-2.1993 3.67,-3.9688 6.18,-5.2969 2.52,-1.3399 5.54,-2.0039 9.04,-2.0039 3.51,0 6.52,0.664 9.02,2.0039 2.49,1.3281 4.54,3.0976 6.15,5.2969 1.6,2.1992 2.77,4.6914 3.55,7.4804 0.77,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.38,5.7578 -1.15,8.5273 -0.78,2.7618 -1.95,5.2461 -3.55,7.4454 -1.61,2.2031 -3.66,3.9648 -6.15,5.3007 -2.5,1.3321 -5.51,2 -9.02,2 -3.5,0 -6.52,-0.6679 -9.04,-2 -2.51,-1.3359 -4.58,-3.0976 -6.18,-5.3007 -1.61,-2.1993 -2.78,-4.6836 -3.57,-7.4454 -0.75,-2.7695 -1.13,-5.6093 -1.13,-8.5273 0,-2.8633 0.38,-5.6914 1.13,-8.4844 z m -4.62,18.9336 c 1.04,3.3125 2.59,6.2383 4.66,8.7813 2.09,2.5429 4.68,4.5742 7.83,6.0781 3.14,1.5078 6.78,2.2578 10.92,2.2578 4.15,0 7.8,-0.75 10.9,-2.2578 3.11,-1.5039 5.7,-3.5352 7.79,-6.0781 2.07,-2.543 3.63,-5.4688 4.66,-8.7813 1.04,-3.3047 1.56,-6.789 1.56,-10.4492 0,-3.6602 -0.52,-7.1406 -1.56,-10.4414 -1.03,-3.3164 -2.59,-6.2305 -4.66,-8.7461 -2.09,-2.5195 -4.68,-4.5312 -7.79,-6.0391 -3.1,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.14,0 -7.78,0.7617 -10.92,2.2656 -3.15,1.5079 -5.74,3.5196 -7.83,6.0391 -2.07,2.5156 -3.62,5.4297 -4.66,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492"
|
||||||
|
id="path3124"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2328.53,81.2695 0,-32.7539 c 0,-3.0625 0.35,-5.664 1.03,-7.8242 0.69,-2.1406 1.71,-3.8984 3.05,-5.2578 1.33,-1.3555 2.97,-2.3477 4.88,-2.9648 1.93,-0.6172 4.11,-0.9219 6.52,-0.9219 2.47,0 4.67,0.3047 6.61,0.9219 1.93,0.6171 3.55,1.6093 4.88,2.9648 1.34,1.3594 2.35,3.1172 3.04,5.2578 0.69,2.1602 1.04,4.7617 1.04,7.8242 l 0,32.7539 5.04,0 0,-33.8672 c 0,-2.7148 -0.38,-5.2968 -1.14,-7.7382 -0.77,-2.4493 -1.99,-4.5899 -3.65,-6.418 -1.66,-1.8242 -3.76,-3.2695 -6.36,-4.332 -2.6,-1.0586 -5.74,-1.5938 -9.46,-1.5938 -3.65,0 -6.77,0.5352 -9.37,1.5938 -2.59,1.0625 -4.71,2.5078 -6.37,4.332 -1.66,1.8281 -2.87,3.9687 -3.63,6.418 -0.77,2.4414 -1.13,5.0234 -1.13,7.7382 l 0,33.8672 5.02,0"
|
||||||
|
id="path3126"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2400.87,56.1484 c 1.51,0 2.99,0.2383 4.36,0.7071 1.39,0.4687 2.59,1.1484 3.63,2.0351 1.03,0.8907 1.86,1.9688 2.49,3.2227 0.62,1.2617 0.92,2.7109 0.92,4.332 0,3.2656 -0.94,5.836 -2.82,7.7031 -1.86,1.8829 -4.73,2.8282 -8.58,2.8282 l -18.83,0 0,-20.8282 18.83,0 z m 0.36,25.1211 c 2.18,0 4.23,-0.2695 6.2,-0.8203 1.94,-0.539 3.64,-1.3633 5.11,-2.4765 1.45,-1.1133 2.61,-2.543 3.47,-4.3008 0.88,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.85,-6.2696 -2.58,-8.7461 -1.73,-2.4688 -4.29,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.73,-0.25 3.17,-0.7032 4.31,-1.3672 1.11,-0.668 2.05,-1.5235 2.77,-2.5586 0.71,-1.0391 1.23,-2.2344 1.55,-3.5977 0.32,-1.3593 0.52,-2.8281 0.63,-4.4062 0.05,-0.8867 0.11,-1.9766 0.16,-3.2656 0.04,-1.2852 0.15,-2.5782 0.29,-3.8868 0.14,-1.3125 0.38,-2.5468 0.7,-3.707 0.31,-1.1562 0.75,-2.0586 1.3,-2.707 l -5.56,0 c -0.29,0.5 -0.53,1.1015 -0.71,1.8203 -0.16,0.7187 -0.29,1.4531 -0.36,2.2265 -0.09,0.7618 -0.14,1.5118 -0.19,2.25 -0.05,0.7461 -0.1,1.3946 -0.15,1.9297 -0.09,1.875 -0.27,3.7461 -0.47,5.6016 -0.23,1.8555 -0.69,3.5 -1.42,4.9609 -0.71,1.4532 -1.75,2.6289 -3.1,3.5235 -1.37,0.8906 -3.23,1.2812 -5.6,1.1875 l -19.12,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
|
||||||
|
id="path3128"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2465.16,70.7109 c -1.02,1.6094 -2.27,2.9493 -3.71,4.0391 -1.47,1.0859 -3.08,1.9141 -4.82,2.4844 -1.75,0.5625 -3.59,0.8515 -5.53,0.8515 -3.5,0 -6.51,-0.6679 -9.03,-2 -2.52,-1.3359 -4.6,-3.0976 -6.18,-5.3007 -1.62,-2.1993 -2.8,-4.6836 -3.58,-7.4454 -0.76,-2.7695 -1.14,-5.6093 -1.14,-8.5273 0,-2.8633 0.38,-5.6914 1.14,-8.4844 0.78,-2.789 1.96,-5.2812 3.58,-7.4804 1.58,-2.1993 3.66,-3.9688 6.18,-5.2969 2.52,-1.3399 5.53,-2.0039 9.03,-2.0039 2.47,0 4.7,0.4453 6.66,1.332 2,0.8906 3.7,2.1016 5.13,3.6289 1.44,1.5274 2.6,3.3281 3.48,5.3711 0.9,2.0508 1.46,4.2695 1.71,6.6367 l 5.04,0 c -0.35,-3.2578 -1.13,-6.1953 -2.3,-8.8203 -1.19,-2.6172 -2.72,-4.8359 -4.59,-6.668 -1.88,-1.8281 -4.09,-3.2304 -6.63,-4.2226 -2.56,-0.9844 -5.37,-1.4844 -8.5,-1.4844 -4.15,0 -7.79,0.7617 -10.93,2.2656 -3.13,1.5079 -5.74,3.5196 -7.83,6.0391 -2.05,2.5156 -3.62,5.4297 -4.65,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492 1.03,3.3125 2.6,6.2383 4.65,8.7813 2.09,2.5429 4.7,4.5742 7.83,6.0781 3.14,1.5078 6.78,2.2578 10.93,2.2578 2.52,0 4.97,-0.3711 7.38,-1.1094 2.39,-0.7382 4.57,-1.8398 6.52,-3.2968 1.95,-1.461 3.57,-3.25 4.89,-5.3711 1.31,-2.1289 2.14,-4.5703 2.48,-7.3399 l -5.04,0 c -0.45,2.0274 -1.17,3.8399 -2.17,5.4492"
|
||||||
|
id="path3130"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 2519.59,81.2695 0,-4.2929 -31.5,0 0,-19.1211 29.5,0 0,-4.3008 -29.5,0 0,-20.8945 31.86,0 0,-4.3047 -36.9,0 0,52.914 36.54,0"
|
||||||
|
id="path3132"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 109.109,130.879 c -2.171,6.246 -5.242,11.781 -9.2145,16.601 -3.9765,4.825 -8.8007,8.7 -14.4765,11.637 -5.6758,2.938 -12.1133,4.395 -19.2969,4.395 -7.375,0 -13.9023,-1.457 -19.5781,-4.395 -5.6797,-2.937 -10.5039,-6.812 -14.4766,-11.637 -3.9726,-4.82 -7.1406,-10.41 -9.5078,-16.75 -2.3633,-6.332 -3.9258,-12.816 -4.6758,-19.433 l 94.7732,0 c -0.179,6.812 -1.371,13.348 -3.547,19.582 z M 20.5703,76.2539 c 1.8008,-6.9141 4.6875,-13.1055 8.6563,-18.5937 3.9765,-5.4883 8.9922,-10.0235 15.0429,-13.6133 6.0547,-3.6016 13.336,-5.3985 21.8516,-5.3985 13.0586,0 23.2734,3.4102 30.6484,10.2188 7.3755,6.8008 12.4885,15.8867 15.3205,27.2344 l 17.883,0 C 126.184,59.457 119.234,46.5898 109.109,37.5195 98.9922,28.4258 84.6602,23.8906 66.1211,23.8906 c -11.5352,0 -21.5234,2.043 -29.9336,6.1133 C 27.7578,34.0664 20.9023,39.6445 15.6094,46.7422 10.3125,53.8281 6.39063,62.0586 3.83203,71.4336 1.28125,80.7891 0,90.6719 0,101.086 c 0,9.641 1.28125,19.098 3.83203,28.371 2.5586,9.27 6.48047,17.551 11.77737,24.828 5.2929,7.285 12.1484,13.153 20.5781,17.606 8.4102,4.437 18.3984,6.664 29.9336,6.664 11.7266,0 21.7539,-2.375 30.0859,-7.102 8.32,-4.719 15.078,-10.922 20.285,-18.578 5.196,-7.66 8.938,-16.461 11.203,-26.395 2.278,-9.937 3.219,-20 2.84,-30.2222 l -112.6522,0 c 0,-6.4336 0.8867,-13.1055 2.6875,-20.0039"
|
||||||
|
id="path3134"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 157.859,174.297 0,-25.258 0.571,0 c 3.41,8.895 9.457,16.039 18.164,21.426 8.695,5.398 18.254,8.09 28.66,8.09 10.219,0 18.777,-1.325 25.68,-3.977 6.91,-2.648 12.441,-6.379 16.601,-11.203 4.16,-4.824 7.098,-10.738 8.797,-17.734 1.699,-7.008 2.555,-14.864 2.555,-23.555 l 0,-94.2188 -17.875,0 0,91.3708 c 0,6.246 -0.567,12.059 -1.703,17.461 -1.141,5.387 -3.121,10.067 -5.957,14.047 -2.844,3.969 -6.676,7.09 -11.5,9.359 -4.821,2.274 -10.829,3.407 -18.02,3.407 -7.191,0 -13.578,-1.278 -19.152,-3.828 -5.578,-2.555 -10.309,-6.055 -14.192,-10.5 -3.879,-4.442 -6.91,-9.743 -9.082,-15.895 -2.172,-6.156 -3.355,-12.82 -3.547,-20.004 l 0,-85.4178 -17.871,0 0,146.4298 17.871,0"
|
||||||
|
id="path3136"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 274.938,174.297 45.976,-128.5509 0.566,0 45.407,128.5509 18.449,0 -54.77,-146.4298 -19.019,0 -56.465,146.4298 19.856,0"
|
||||||
|
id="path3138"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 409.707,174.297 0,-146.4298 -17.875,0 0,146.4298 17.875,0 z m 0,56.187 0,-28.664 -17.875,0 0,28.664 17.875,0"
|
||||||
|
id="path3140"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 487.02,104.863 c 3.683,0 7.222,0.27 10.632,0.809 3.407,0.547 6.403,1.601 8.993,3.172 2.589,1.566 4.668,3.785 6.238,6.648 1.558,2.852 2.347,6.613 2.347,11.235 0,4.636 -0.789,8.39 -2.347,11.25 -1.57,2.859 -3.649,5.074 -6.238,6.64 -2.59,1.571 -5.586,2.629 -8.993,3.172 -3.41,0.547 -6.949,0.813 -10.632,0.813 l -24.934,0 0,-43.739 24.934,0 z m 8.793,68.68 c 9.128,0 16.898,-1.32 23.304,-3.988 6.406,-2.66 11.613,-6.16 15.633,-10.524 4.023,-4.359 6.961,-9.34 8.797,-14.922 1.84,-5.589 2.758,-11.379 2.758,-17.382 0,-5.852 -0.918,-11.614 -2.758,-17.27 -1.836,-5.652 -4.774,-10.664 -8.797,-15.0273 -4.02,-4.3633 -9.227,-7.8672 -15.633,-10.5234 -6.406,-2.6602 -14.176,-3.9844 -23.304,-3.9844 l -33.727,0 0,-52.336 -32.102,0 0,145.9571 65.829,0"
|
||||||
|
id="path3142"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 614.273,76.7539 c -1.835,-0.6211 -3.812,-1.1289 -5.929,-1.5351 -2.114,-0.4102 -4.324,-0.75 -6.641,-1.0196 -2.316,-0.2773 -4.637,-0.6211 -6.953,-1.0273 -2.176,-0.4102 -4.328,-0.9571 -6.437,-1.6328 -2.114,-0.6836 -3.958,-1.6016 -5.52,-2.7618 -1.566,-1.1562 -2.832,-2.625 -3.777,-4.3945 -0.957,-1.7773 -1.438,-4.0234 -1.438,-6.7461 0,-2.5937 0.481,-4.7695 1.438,-6.5429 0.945,-1.7696 2.246,-3.168 3.879,-4.1915 1.636,-1.0234 3.543,-1.7382 5.726,-2.1484 2.176,-0.4101 4.426,-0.6094 6.746,-0.6094 5.723,0 10.145,0.9532 13.285,2.8672 3.133,1.9024 5.45,4.1875 6.953,6.8438 1.497,2.6562 2.418,5.3476 2.758,8.0742 0.336,2.7226 0.516,4.9101 0.516,6.543 l 0,10.8398 c -1.234,-1.1055 -2.766,-1.9492 -4.606,-2.5586 z m -57.339,40.9841 c 3,4.496 6.812,8.106 11.449,10.832 4.637,2.723 9.844,4.672 15.637,5.828 5.789,1.157 11.617,1.735 17.48,1.735 5.316,0 10.691,-0.375 16.145,-1.125 5.457,-0.75 10.425,-2.211 14.921,-4.395 4.504,-2.175 8.18,-5.207 11.043,-9.093 2.86,-3.883 4.297,-9.032 4.297,-15.434 l 0,-54.9922 c 0,-4.7774 0.27,-9.336 0.817,-13.6954 0.539,-4.3632 1.496,-7.6328 2.859,-9.8125 l -29.437,0 c -0.543,1.6329 -0.989,3.3008 -1.329,5.0118 -0.343,1.6992 -0.582,3.4414 -0.718,5.2109 -4.633,-4.7734 -10.082,-8.1133 -16.348,-10.0156 -6.273,-1.9063 -12.676,-2.8672 -19.219,-2.8672 -5.043,0 -9.746,0.6172 -14.105,1.8398 -4.367,1.2266 -8.18,3.1367 -11.449,5.7305 -3.27,2.5859 -5.825,5.8594 -7.668,9.8125 -1.84,3.9492 -2.758,8.6523 -2.758,14.1016 0,5.9961 1.058,10.9375 3.168,14.8242 2.109,3.8789 4.836,6.9726 8.179,9.2969 3.34,2.3164 7.157,4.0585 11.446,5.2148 4.297,1.1562 8.617,2.0742 12.98,2.7539 4.364,0.6836 8.656,1.2266 12.879,1.6367 4.227,0.4102 7.973,1.0235 11.25,1.8438 3.266,0.8125 5.856,2.0117 7.762,3.582 1.91,1.5664 2.793,3.8477 2.664,6.8435 0,3.137 -0.516,5.617 -1.535,7.461 -1.028,1.844 -2.395,3.266 -4.09,4.293 -1.707,1.02 -3.68,1.699 -5.93,2.039 -2.25,0.344 -4.672,0.516 -7.258,0.516 -5.718,0 -10.222,-1.223 -13.488,-3.68 -3.277,-2.453 -5.183,-6.543 -5.726,-12.265 l -29.032,0 c 0.41,6.816 2.118,12.46 5.114,16.968"
|
||||||
|
id="path3144"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 720.172,133.277 0,-19.422 -21.254,0 0,-52.3355 c 0,-4.9023 0.812,-8.1718 2.449,-9.8047 1.637,-1.6406 4.903,-2.4609 9.817,-2.4609 1.632,0 3.195,0.0703 4.699,0.2031 1.496,0.1328 2.926,0.3438 4.289,0.6172 l 0,-22.4883 c -2.453,-0.414 -5.176,-0.6836 -8.176,-0.8203 -2.996,-0.1289 -5.926,-0.1992 -8.789,-0.1992 -4.5,0 -8.754,0.3047 -12.773,0.918 -4.024,0.6094 -7.563,1.8086 -10.629,3.5781 -3.071,1.7695 -5.489,4.293 -7.254,7.5586 -1.781,3.2773 -2.664,7.5703 -2.664,12.8828 l 0,62.3511 -17.578,0 0,19.422 17.578,0 0,31.684 29.031,0 0,-31.684 21.254,0"
|
||||||
|
id="path3146"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 755.918,173.543 0,-54.984 0.613,0 c 3.684,6.125 8.387,10.586 14.11,13.379 5.722,2.796 11.312,4.195 16.761,4.195 7.77,0 14.137,-1.059 19.114,-3.164 4.972,-2.114 8.894,-5.043 11.754,-8.793 2.863,-3.75 4.871,-8.317 6.031,-13.699 1.152,-5.383 1.734,-11.3481 1.734,-17.8832 l 0,-65.0079 -29.027,0 0,59.6954 c 0,8.7148 -1.363,15.2227 -4.086,19.5157 -2.731,4.293 -7.567,6.433 -14.516,6.433 -7.902,0 -13.633,-2.343 -17.172,-7.039 -3.543,-4.703 -5.316,-12.441 -5.316,-23.2144 l 0,-55.3907 -29.023,0 0,145.9571 29.023,0"
|
||||||
|
id="path3148"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 863.082,0 21.875,0 0,190.547 -21.875,0 0,-190.547 z"
|
||||||
|
id="path3150"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 41 KiB |
59
static/images/logo-short-white.svg
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="103.4625"
|
||||||
|
height="25.825001"
|
||||||
|
id="svg3227"
|
||||||
|
xml:space="preserve"><metadata
|
||||||
|
id="metadata3233"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs3231"><filter
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="1"
|
||||||
|
height="1"
|
||||||
|
color-interpolation-filters="sRGB"
|
||||||
|
id="filter3283"><feColorMatrix
|
||||||
|
id="feColorMatrix3285"
|
||||||
|
result="fbSourceGraphic"
|
||||||
|
values="1"
|
||||||
|
type="saturate" /><feColorMatrix
|
||||||
|
id="feColorMatrix3287"
|
||||||
|
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0 "
|
||||||
|
in="fbSourceGraphic" /></filter></defs><g
|
||||||
|
transform="matrix(1.25,0,0,-1.25,0,25.825)"
|
||||||
|
id="g3235"><g
|
||||||
|
transform="scale(0.1,0.1)"
|
||||||
|
id="g3237"
|
||||||
|
style="filter:url(#filter3283)"><path
|
||||||
|
d="m 109.105,106.992 c -2.167,6.25 -5.234,11.797 -9.2183,16.602 -3.9648,4.824 -8.789,8.711 -14.4726,11.64 -5.6719,2.95 -12.1094,4.395 -19.2969,4.395 -7.3711,0 -13.9063,-1.445 -19.5703,-4.395 -5.6836,-2.929 -10.5078,-6.816 -14.4805,-11.64 -3.9766,-4.805 -7.1406,-10.41 -9.5039,-16.739 -2.3633,-6.328 -3.9258,-12.812 -4.6875,-19.4331 l 94.785,0 c -0.183,6.8164 -1.375,13.3401 -3.555,19.5701 z M 20.5703,52.3828 c 1.7969,-6.914 4.6875,-13.1055 8.6524,-18.5937 3.9843,-5.4883 8.9961,-10.0391 15.039,-13.6133 6.0547,-3.6133 13.3399,-5.4102 21.8555,-5.4102 13.0664,0 23.2812,3.418 30.6445,10.2149 7.3833,6.8164 12.5003,15.8984 15.3323,27.2461 l 17.871,0 C 126.176,35.5859 119.234,22.7148 109.105,13.6328 98.9883,4.55078 84.6523,0.0195313 66.1172,0.0195313 54.5859,0.0195313 44.5938,2.05078 36.1875,6.13281 27.7578,10.1953 20.9023,15.7617 15.6094,22.8711 10.3164,29.9414 6.39063,38.1836 3.83203,47.5586 1.27344,56.9141 0.00390625,66.7969 0.00390625,77.207 c 0,9.6485 1.26953375,19.1016 3.82812375,28.379 2.5586,9.258 6.48437,17.539 11.77737,24.824 5.2929,7.285 12.1484,13.145 20.5781,17.598 8.4063,4.433 18.3984,6.66 29.9297,6.66 11.7305,0 21.7578,-2.363 30.0898,-7.09 8.32,-4.726 15.078,-10.918 20.281,-18.574 5.196,-7.676 8.946,-16.465 11.211,-26.406 2.266,-9.9417 3.215,-20.0003 2.832,-30.2152 l -112.656,0 c 0,-6.4453 0.8984,-13.1055 2.6953,-20"
|
||||||
|
id="path3239"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 157.855,150.41 0,-25.254 0.567,0 c 3.418,8.907 9.465,16.035 18.164,21.426 8.703,5.41 18.262,8.086 28.672,8.086 10.215,0 18.769,-1.309 25.676,-3.965 6.902,-2.656 12.441,-6.387 16.601,-11.211 4.16,-4.824 7.098,-10.742 8.797,-17.734 1.699,-7.012 2.559,-14.863 2.559,-23.5549 l 0,-94.21872 -17.879,0 0,91.36722 c 0,6.2504 -0.567,12.0704 -1.711,17.4614 -1.133,5.39 -3.113,10.078 -5.957,14.062 -2.832,3.965 -6.668,7.09 -11.492,9.355 -4.817,2.266 -10.832,3.399 -18.02,3.399 -7.187,0 -13.574,-1.27 -19.16,-3.828 -5.567,-2.559 -10.301,-6.055 -14.18,-10.488 -3.887,-4.454 -6.914,-9.747 -9.082,-15.899 -2.176,-6.152 -3.359,-12.832 -3.555,-19.9999 l 0,-85.42972 -17.871,0 0,146.42562 17.871,0"
|
||||||
|
id="path3241"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 274.938,150.41 45.976,-128.535 0.566,0 45.399,128.535 18.457,0 -54.766,-146.42562 -19.023,0 -56.465,146.42562 19.856,0"
|
||||||
|
id="path3243"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 409.711,150.41 0,-146.42562 -17.879,0 0,146.42562 17.879,0 z m 0,56.192 0,-28.653 -17.879,0 0,28.653 17.879,0"
|
||||||
|
id="path3245"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 488.297,80.9766 c 3.68,0 7.215,0.2734 10.633,0.8203 3.398,0.5469 6.398,1.6015 8.984,3.164 2.598,1.5625 4.668,3.7891 6.242,6.6602 1.563,2.8516 2.352,6.6016 2.352,11.2309 0,4.628 -0.789,8.378 -2.352,11.25 -1.574,2.851 -3.644,5.078 -6.242,6.64 -2.586,1.563 -5.586,2.617 -8.984,3.164 -3.418,0.547 -6.953,0.821 -10.633,0.821 l -24.934,0 0,-43.7504 24.934,0 z m 8.789,68.6914 c 9.129,0 16.902,-1.328 23.309,-3.984 6.406,-2.676 11.613,-6.172 15.625,-10.528 4.023,-4.355 6.964,-9.336 8.8,-14.922 1.844,-5.586 2.762,-11.386 2.762,-17.382 0,-5.8598 -0.918,-11.6215 -2.762,-17.2661 -1.836,-5.664 -4.777,-10.664 -8.8,-15.039 -4.012,-4.3555 -9.219,-7.8711 -15.625,-10.5274 -6.407,-2.6562 -14.18,-3.9843 -23.309,-3.9843 l -33.723,0 0,-52.32426 -32.109,0 0,145.95706 65.832,0"
|
||||||
|
id="path3247"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 615.551,52.8711 c -1.836,-0.625 -3.817,-1.1328 -5.938,-1.5234 -2.109,-0.4102 -4.316,-0.7618 -6.64,-1.0352 -2.313,-0.2734 -4.629,-0.6055 -6.953,-1.0156 -2.168,-0.4102 -4.325,-0.9571 -6.434,-1.6406 -2.109,-0.6836 -3.957,-1.6016 -5.52,-2.754 -1.562,-1.1523 -2.832,-2.6367 -3.777,-4.3945 -0.957,-1.7773 -1.437,-4.0234 -1.437,-6.7578 0,-2.5781 0.48,-4.7656 1.437,-6.543 0.945,-1.7578 2.246,-3.164 3.875,-4.1797 1.641,-1.0351 3.547,-1.7382 5.734,-2.1484 2.176,-0.4101 4.422,-0.6055 6.747,-0.6055 5.722,0 10.136,0.9375 13.281,2.8516 3.137,1.9141 5.449,4.1992 6.953,6.8555 1.496,2.6562 2.422,5.3515 2.754,8.0664 0.344,2.7344 0.519,4.9219 0.519,6.5429 l 0,10.8399 c -1.23,-1.0938 -2.765,-1.9531 -4.601,-2.5586 z m -57.344,40.9961 c 3,4.4922 6.816,8.1058 11.445,10.8208 4.641,2.734 9.844,4.667 15.645,5.839 5.781,1.153 11.613,1.719 17.48,1.719 5.313,0 10.684,-0.371 16.145,-1.113 5.457,-0.762 10.418,-2.207 14.922,-4.395 4.5,-2.187 8.172,-5.215 11.043,-9.1013 2.851,-3.8867 4.297,-9.0234 4.297,-15.4297 l 0,-55 c 0,-4.7656 0.265,-9.3359 0.812,-13.6914 0.535,-4.35544 1.492,-7.63669 2.859,-9.80466 l -29.433,0 c -0.547,1.62109 -0.996,3.30078 -1.328,5 -0.352,1.69926 -0.586,3.45706 -0.723,5.21486 C 616.742,9.16016 611.293,5.82031 605.023,3.90625 598.754,2.01172 592.348,1.05469 585.805,1.05469 c -5.039,0 -9.746,0.60547 -14.102,1.83594 -4.375,1.23046 -8.183,3.125 -11.453,5.72265 -3.273,2.59762 -5.82,5.85942 -7.668,9.82422 -1.836,3.9453 -2.754,8.6523 -2.754,14.1016 0,5.9961 1.055,10.9375 3.164,14.8242 2.11,3.8672 4.836,6.9726 8.184,9.2969 3.34,2.3046 7.148,4.0429 11.445,5.2148 4.297,1.1523 8.613,2.0703 12.981,2.7539 4.363,0.6836 8.652,1.2109 12.878,1.6211 4.219,0.4102 7.969,1.0352 11.25,1.8555 3.262,0.8008 5.852,2.0117 7.754,3.5742 1.914,1.5625 2.793,3.8476 2.668,6.8555 0,3.125 -0.519,5.6054 -1.535,7.4609 -1.023,1.8359 -2.39,3.2617 -4.09,4.2773 -1.711,1.0352 -3.683,1.6993 -5.929,2.0508 -2.246,0.3321 -4.668,0.5078 -7.254,0.5078 -5.723,0 -10.227,-1.2109 -13.489,-3.6718 -3.281,-2.461 -5.183,-6.543 -5.73,-12.2657 l -29.035,0 c 0.41,6.8164 2.121,12.461 5.117,16.9727"
|
||||||
|
id="path3249"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 721.449,109.395 0,-19.4145 -21.258,0 0,-52.3438 c 0,-4.9023 0.809,-8.164 2.45,-9.8047 1.64,-1.6406 4.902,-2.4609 9.816,-2.4609 1.629,0 3.191,0.0781 4.695,0.2148 1.504,0.1172 2.93,0.3321 4.297,0.6055 l 0,-22.48046 c -2.461,-0.41016 -5.176,-0.6836 -8.183,-0.82031 -2.989,-0.13672 -5.918,-0.19532 -8.789,-0.19532 -4.493,0 -8.75,0.29297 -12.774,0.91797 -4.023,0.60547 -7.558,1.79688 -10.625,3.57422 -3.066,1.75781 -5.488,4.2969 -7.254,7.5586 -1.777,3.2812 -2.668,7.5586 -2.668,12.8711 l 0,62.3633 -17.578,0 0,19.4145 17.578,0 0,31.679 29.035,0 0,-31.679 21.258,0"
|
||||||
|
id="path3251"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||||
|
d="m 757.621,149.668 0,-54.9805 0.606,0 c 3.691,6.1135 8.39,10.5855 14.113,13.3785 5.723,2.793 11.316,4.18 16.765,4.18 7.766,0 14.133,-1.055 19.102,-3.164 4.981,-2.109 8.906,-5.039 11.758,-8.789 2.871,-3.75 4.875,-8.3203 6.035,-13.6914 1.152,-5.3907 1.738,-11.3477 1.738,-17.8907 l 0,-64.99996 -29.031,0 0,59.68746 c 0,8.711 -1.359,15.2344 -4.094,19.5118 -2.722,4.2968 -7.558,6.4453 -14.511,6.4453 -7.899,0 -13.633,-2.3438 -17.168,-7.0508 -3.543,-4.6875 -5.313,-12.4414 -5.313,-23.2031 l 0,-55.39066 -29.023,0 0,145.95706 29.023,0"
|
||||||
|
id="path3253"
|
||||||
|
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 8.2 KiB |
BIN
static/images/uoa.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
static/images/wait.gif
Normal file
|
After Width: | Height: | Size: 814 B |
1782
static/js/d3-pathway.js
vendored
Normal file
217
static/js/epp/epp.js
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
class Setting {
|
||||||
|
constructor(nameInputId, selectedPackagesId, relativeReasoningSelectId, cutoffInputId, evaluationTypeSelectId,
|
||||||
|
availableTSSelectId, formsId, tableId, summaryTableId) {
|
||||||
|
this.nameInputId = nameInputId;
|
||||||
|
this.selectedPackagesId = selectedPackagesId;
|
||||||
|
this.relativeReasoningSelectId = relativeReasoningSelectId;
|
||||||
|
this.cutoffInputId = cutoffInputId;
|
||||||
|
this.evaluationTypeSelectId = evaluationTypeSelectId;
|
||||||
|
this.availableTSSelectId = availableTSSelectId;
|
||||||
|
this.formsId = formsId;
|
||||||
|
this.tableId = tableId;
|
||||||
|
this.summaryTableId = summaryTableId;
|
||||||
|
|
||||||
|
// General settings
|
||||||
|
this.name = null;
|
||||||
|
this.selectedPackages = [];
|
||||||
|
|
||||||
|
// Relative Reasoning related
|
||||||
|
this.selectedRelativeReasoning = null;
|
||||||
|
this.cutoff = null;
|
||||||
|
this.evaluationType = null;
|
||||||
|
|
||||||
|
// parameters such as { "lowPH": 7, "highPH": 8 }
|
||||||
|
this.tsParams = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
extractName() {
|
||||||
|
var tempName = $('#' + this.nameInputId).val()
|
||||||
|
if (tempName == '') {
|
||||||
|
console.log("Name was empty...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.name = tempName;
|
||||||
|
}
|
||||||
|
|
||||||
|
extractSelectedPackages() {
|
||||||
|
var selPacks = $("#" + this.selectedPackagesId + " :selected");
|
||||||
|
var ref = this;
|
||||||
|
ref.selectedPackages = [];
|
||||||
|
selPacks.each(function () {
|
||||||
|
var obj = {}
|
||||||
|
obj['id'] = this.value;
|
||||||
|
obj['name'] = this.text;
|
||||||
|
ref.selectedPackages.push(obj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
extractRelativeReasoning() {
|
||||||
|
var tempRR = $('#' + this.relativeReasoningSelectId + " :selected").val()
|
||||||
|
if (tempRR == '') {
|
||||||
|
console.log("RR was empty...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var obj = {}
|
||||||
|
obj['id'] = $('#' + this.relativeReasoningSelectId + " :selected").val()
|
||||||
|
obj['name'] = $('#' + this.relativeReasoningSelectId + " :selected").text()
|
||||||
|
this.selectedRelativeReasoning = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
extractCutoff() {
|
||||||
|
var tempCutoff = $('#' + this.cutoffInputId).val()
|
||||||
|
if (tempCutoff == '') {
|
||||||
|
console.log("Cutoff was empty...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.cutoff = tempCutoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
extractEvaluationType() {
|
||||||
|
var tempEvaluationType = $('#' + this.evaluationTypeSelectId).val()
|
||||||
|
if (tempEvaluationType == '') {
|
||||||
|
console.log("EvaluationType was empty...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.evaluationType = tempEvaluationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTruncator() {
|
||||||
|
// triggered by "Add"
|
||||||
|
// will extract values and afterwards updates table + summary
|
||||||
|
var type = $("#" + this.availableTSSelectId + " :selected").val();
|
||||||
|
var text = $("#" + this.availableTSSelectId + " :selected").text();
|
||||||
|
var form = $("#" + type + "_form > :input")
|
||||||
|
|
||||||
|
// flag to check whether at least one input had a valid value
|
||||||
|
var addedValue = false;
|
||||||
|
|
||||||
|
// reference being used in each
|
||||||
|
var ref = this;
|
||||||
|
|
||||||
|
form.each(function() {
|
||||||
|
if(this.value == "" || this.value === "undefined"){
|
||||||
|
console.log(this);
|
||||||
|
console.log("Skipping " + this.name);
|
||||||
|
} else {
|
||||||
|
var obj = {}
|
||||||
|
obj[this.name] = this.value;
|
||||||
|
obj['text'] = text;
|
||||||
|
ref.tsParams[type] = obj
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateTable();
|
||||||
|
this.updateSummaryTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTruncator(rowId) {
|
||||||
|
var summary = rowId.startsWith("sum") ? true : false;
|
||||||
|
// plain key
|
||||||
|
var key = rowId.replace(summary ? "sum" : "trunc", "").replace("row", "");
|
||||||
|
|
||||||
|
console.log("Removing " + key);
|
||||||
|
|
||||||
|
// remove the rows
|
||||||
|
$("#trunc"+ key + "row").remove();
|
||||||
|
if($("#sum"+ key + "row").length > 0) {
|
||||||
|
$("#sum"+ key + "row").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this.tsParams[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTable() {
|
||||||
|
// remove all children
|
||||||
|
$('#'+this.tableId + "Body").empty()
|
||||||
|
|
||||||
|
var innerHTML = "<tr>" +
|
||||||
|
"<td>Name</td>" +
|
||||||
|
"<td>Value</td>" +
|
||||||
|
"<td width='10%'>Action</td>" +
|
||||||
|
"</tr>";
|
||||||
|
|
||||||
|
for (var x in this.tsParams) {
|
||||||
|
var val = "";
|
||||||
|
for (var y in this.tsParams[x]){
|
||||||
|
if (y == 'text') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
val += this.tsParams[x][y]
|
||||||
|
|
||||||
|
}
|
||||||
|
innerHTML += "<tr id='trunc" + x + "row'>" +
|
||||||
|
"<td>" + this.tsParams[x]['text'] + "</td>" +
|
||||||
|
"<td>" + val + "</td>" +
|
||||||
|
"<td width='10%'>"+
|
||||||
|
"<button type='button' id='" + x + "button' class='form-control' onclick='s.removeTruncator(\"trunc" + x + "row\")'>Remove</button>" +
|
||||||
|
"</td>" +
|
||||||
|
"</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#'+this.tableId + "Body").append(innerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
packageRows() {
|
||||||
|
var res = '';
|
||||||
|
for(var p in this.selectedPackages) {
|
||||||
|
var obj = this.selectedPackages[p];
|
||||||
|
res += "<tr>" +
|
||||||
|
"<td>Package</td>" +
|
||||||
|
"<td>" + obj['name'] + "</td>" +
|
||||||
|
"<td width='10%'>" +
|
||||||
|
// "<button type='button' id='relativereasoningbutton' class='form-control' onclick='s.removeTruncator(\"Relative Reasoning\")'>Remove</button>" +
|
||||||
|
"</td>" +
|
||||||
|
"</tr>";
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
modelRow() {
|
||||||
|
if(this.selectedRelativeReasoning == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return "<tr>" +
|
||||||
|
"<td>Relative Reasoning</td>" +
|
||||||
|
"<td>" + this.selectedRelativeReasoning['name'] + " " + (this.evaluationType == "singleGen" ? "SG" : "MG") + " with t=" + this.cutoff + " </td>" +
|
||||||
|
"<td width='10%'>" +
|
||||||
|
// "<button type='button' id='relativereasoningbutton' class='form-control' onclick='s.removeTruncator(\"Relative Reasoning\")'>Remove</button>" +
|
||||||
|
"</td>" +
|
||||||
|
"</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSummaryTable() {
|
||||||
|
// remove all children
|
||||||
|
$('#'+this.summaryTableId + "Body").empty()
|
||||||
|
|
||||||
|
var innerHTML = "<tr>" +
|
||||||
|
"<td>Name</td>" +
|
||||||
|
"<td>Value</td>" +
|
||||||
|
"<td width='10%'>Action</td>" +
|
||||||
|
"</tr>";
|
||||||
|
|
||||||
|
innerHTML += this.packageRows();
|
||||||
|
innerHTML += this.modelRow();
|
||||||
|
|
||||||
|
// var innerHTML = ''
|
||||||
|
for (var x in this.tsParams) {
|
||||||
|
var val = "";
|
||||||
|
for (var y in this.tsParams[x]){
|
||||||
|
if (y == 'text') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
val += this.tsParams[x][y]
|
||||||
|
|
||||||
|
}
|
||||||
|
innerHTML += "<tr id='sum" + x + "row'>" +
|
||||||
|
"<td>" + this.tsParams[x]['text'] + "</td>" +
|
||||||
|
"<td>" + val + "</td>" +
|
||||||
|
"<td width='10%'>"+
|
||||||
|
"<button type='button' id='" + x + "button' class='form-control' onclick='s.removeTruncator(\"sum" + x + "row\")'>Remove</button>" +
|
||||||
|
"</td>" +
|
||||||
|
"</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#'+this.summaryTableId + "Body").append(innerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
static/js/epp/jquery-bootstrap-modal-steps.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
!function(t){"use strict";t.fn.modalSteps=function(a){var e=this,n=t.extend({btnCancelHtml:"Cancel",btnPreviousHtml:"Previous",btnNextHtml:"Next",btnLastStepHtml:"Complete",disableNextButton:!1,completeCallback:function(){},callbacks:{},getTitleAndStep:function(){}},a),d=function(t){return void 0!==t&&"function"==typeof t&&(t(),!0)};return e.on("show.bs.modal",function(){var a,l,s,i=e.find(".modal-footer"),o=i.find(".js-btn-step[data-orientation=cancel]"),p=i.find(".js-btn-step[data-orientation=previous]"),r=i.find(".js-btn-step[data-orientation=next]"),c=n.callbacks["*"],b=n.callbacks[1];n.disableNextButton&&r.attr("disabled","disabled"),p.attr("disabled","disabled"),function(){var t=n.callbacks["*"];if(void 0!==t&&"function"!=typeof t)throw"everyStepCallback is not a function! I need a function";if("function"!=typeof n.completeCallback)throw"completeCallback is not a function! I need a function";for(var a in n.callbacks)if(n.callbacks.hasOwnProperty(a)){var e=n.callbacks[a];if("*"!==a&&void 0!==e&&"function"!=typeof e)throw"Step "+a+" callback must be a function"}}(),d(c),d(b),o.html(n.btnCancelHtml),p.html(n.btnPreviousHtml),r.html(n.btnNextHtml),a=t("<input>").attr({type:"hidden",id:"actual-step",value:"1"}),e.find("#actual-step").remove(),e.append(a),e.find("[data-step=1]").removeClass("d-none"),r.attr("data-step",2),l=e.find("[data-step=1]").data("title"),s=t("<span>").addClass("label label-success").html(1),e.find(".js-title-step").append(s).append(" "+l),n.getTitleAndStep(a.attr("data-title"),1)}).on("hidden.bs.modal",function(){var t=e.find("#actual-step"),a=e.find(".js-btn-step[data-orientation=next]");e.find("[data-step]").not(e.find(".js-btn-step")).addClass("d-none"),t.not(e.find(".js-btn-step")).remove(),a.attr("data-step",1).html(n.btnNextHtml),e.find(".js-title-step").html("")}),e.find(".js-btn-step").on("click",function(){var a,l,s,i,o=t(this),p=e.find("#actual-step"),r=e.find(".js-btn-step[data-orientation=previous]"),c=e.find(".js-btn-step[data-orientation=next]"),b=e.find(".js-title-step"),f=o.data("orientation"),u=parseInt(p.val()),m=n.callbacks["*"];if(a=e.find("div[data-step]").length,"complete"===o.attr("data-step"))return n.completeCallback(),void e.modal("hide");if("next"===f)l=u+1,r.attr("data-step",u),p.val(l);else{if("previous"!==f)return void e.modal("hide");l=u-1,c.attr("data-step",u),r.attr("data-step",l-1),p.val(u-1)}parseInt(p.val())===a?c.attr("data-step","complete").html(n.btnLastStepHtml):c.attr("data-step",l).html(n.btnNextHtml),n.disableNextButton&&c.attr("disabled","disabled"),e.find("[data-step="+u+"]").not(e.find(".js-btn-step")).addClass("d-none"),e.find("[data-step="+l+"]").not(e.find(".js-btn-step")).removeClass("d-none"),parseInt(r.attr("data-step"))>0?r.removeAttr("disabled"):r.attr("disabled","disabled"),"previous"===f&&c.removeAttr("disabled"),(s=e.find("[data-step="+l+"]")).attr("data-unlock-continue")&&c.removeAttr("disabled"),i=s.attr("data-title");var v=t("<span>").addClass("label label-success").html(l);b.html(v).append(" "+i),n.getTitleAndStep(s.attr("data-title"),l);var h=n.callbacks[p.val()];d(m),d(h)}),this}}(jQuery);
|
||||||
191
static/js/epp/pathway.js
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// drawarea
|
||||||
|
var svg;
|
||||||
|
|
||||||
|
// colors
|
||||||
|
var border = "thin solid lightgrey";
|
||||||
|
|
||||||
|
|
||||||
|
// nodes
|
||||||
|
|
||||||
|
|
||||||
|
// links
|
||||||
|
|
||||||
|
// tree
|
||||||
|
var compoundSize = 75; // = diameter of nodes of small compounds
|
||||||
|
var maxCompoundSize = function(){ return 1.5 * compoundSize }; // limit increased size of larger compounds
|
||||||
|
var maxCompoundPopupSize = function(){ return 5 * compoundSize };
|
||||||
|
var compoundImageMargin = 0.15; // compounds images are rectangles, reduce size to fit into nodes
|
||||||
|
|
||||||
|
var treeLayoutLinkDistance = function(){ return 2.5 * compoundSize };
|
||||||
|
var treeLayoutCharge = -1000; // compounds push each other apart
|
||||||
|
var treeLayoutPseudoCharge = treeLayoutCharge * 0.1; // relaxes forces on pseudo nodes
|
||||||
|
var treeLayoutLevelGravity = 0.5; // pulls compounds back to their depth level
|
||||||
|
var treeLayoutXCenterGravity = 0.03; // pulls compounds to x center
|
||||||
|
var treeLayoutLevels = function(){ return treeLayoutLinkDistance() }; // distance between depth levels
|
||||||
|
var treeLayoutXInitMargin = function(){ return treeLayoutLinkDistance() }; // initial x-distance between nodes of same depth
|
||||||
|
|
||||||
|
|
||||||
|
// Convenience method to check wether a given node is a leaf
|
||||||
|
function isLeaf(node) {
|
||||||
|
return node.children.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMaxNodeDepth(graph) {
|
||||||
|
maxDepth = 0
|
||||||
|
graph.nodes.forEach(function(node, i) {
|
||||||
|
if (node.depth > maxDepth)
|
||||||
|
maxDepth = node.depth;
|
||||||
|
});
|
||||||
|
return maxDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds a parents[] and a children[] array for each node set treeSize
|
||||||
|
function setChildrenAndParents(graph) {
|
||||||
|
// Init arrays on each node
|
||||||
|
graph.nodes.forEach(function(node, i) {
|
||||||
|
node.parents = []
|
||||||
|
node.children = []
|
||||||
|
});
|
||||||
|
|
||||||
|
// loop through links, determine source and target and populate
|
||||||
|
// their parent/children arrays
|
||||||
|
graph.links.forEach(function(link, j) {
|
||||||
|
source = graph.nodes[link.source];
|
||||||
|
target = graph.nodes[link.target];
|
||||||
|
source.children.push(target);
|
||||||
|
target.parents.push(source);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recursively traverses the tree to count the nodes that can be reached
|
||||||
|
function count(node) {
|
||||||
|
node.touch = true;
|
||||||
|
c = 1;
|
||||||
|
node.children.forEach(function(child, i) {
|
||||||
|
if (!child.touch) {
|
||||||
|
c += count(child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
node.count = false;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check how many nodes can be reached from each given node
|
||||||
|
// by traversing the children property
|
||||||
|
graph.nodes.forEach(function(node, i) {
|
||||||
|
// reset touch on all nodes
|
||||||
|
graph.nodes.forEach(function(n, i) {
|
||||||
|
n.touch = false;
|
||||||
|
});
|
||||||
|
// get count
|
||||||
|
node.downstreamNodeCount = count(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
// determine siblings for each links
|
||||||
|
graph.links.forEach(function(link, j) {
|
||||||
|
p = graph.nodes[link.source].children.length;
|
||||||
|
c = graph.nodes[link.target].parents.length;
|
||||||
|
link.siblings = Math.max(p, c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets all links to pseudo that are connected to a pseudo node
|
||||||
|
function setPseudoLinks(graph) {
|
||||||
|
graph.links.forEach(function(link, j) {
|
||||||
|
if (graph.nodes[link.source].pseudo || graph.nodes[link.target].pseudo) {
|
||||||
|
link.pseudo = true;
|
||||||
|
} else {
|
||||||
|
link.pseudo = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function redraw() {
|
||||||
|
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
// remove all;
|
||||||
|
svg.selectAll("g.node").remove();
|
||||||
|
svg.selectAll("rect").remove();
|
||||||
|
svg.selectAll("image").remove();
|
||||||
|
svg.selectAll("line").remove();
|
||||||
|
svg.selectAll("marker").remove();
|
||||||
|
svg.selectAll("text").remove();
|
||||||
|
svg.selectAll("g").remove();
|
||||||
|
svg.selectAll("defs").remove();
|
||||||
|
//start(__graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _drawPathway(elem, graph, options) {
|
||||||
|
width = document.getElementById(elem).offsetWidth;
|
||||||
|
height = width * 0.75;
|
||||||
|
|
||||||
|
svg = d3.select("#" + elem)
|
||||||
|
.append("svg")
|
||||||
|
.attr("width", "100%")
|
||||||
|
.attr("height", height)
|
||||||
|
.style("border", border)
|
||||||
|
.attr("inline", "block")
|
||||||
|
.attr("margin", "auto")
|
||||||
|
.attr("pointer-events", "all")
|
||||||
|
.append('svg:g')
|
||||||
|
.call(d3.behavior.zoom().on("zoom", redraw))
|
||||||
|
.attr("preserveAspectRatio", "xMidYMid meet")
|
||||||
|
.append('svg:g');
|
||||||
|
|
||||||
|
// Background rectangle to enable zoom function
|
||||||
|
// even cursor is not on a node or path
|
||||||
|
svg.append('svg:rect')
|
||||||
|
.attr('width', 9 * width)
|
||||||
|
.attr('height', 9 * height)
|
||||||
|
.attr('fill', 'white')
|
||||||
|
.attr("x", -4 * width)
|
||||||
|
.attr("y", -4 * height)
|
||||||
|
.attr("opacity", 0.0);
|
||||||
|
|
||||||
|
setChildrenAndParents(graph)
|
||||||
|
setPseudoLinks(graph)
|
||||||
|
var maxDepths = getMaxNodeDepth(graph);
|
||||||
|
|
||||||
|
// if(options.treeLayout) {
|
||||||
|
//
|
||||||
|
// } else {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
force = d3.layout.force()
|
||||||
|
.charge(function(d) {
|
||||||
|
if (d.pseudo) {
|
||||||
|
return treeLayoutPseudoCharge;
|
||||||
|
} else {
|
||||||
|
return treeLayoutCharge;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.gravity(0)
|
||||||
|
.linkDistance(function(d) {
|
||||||
|
dist = Math.abs(d.target.treeLevel - d.source.treeLevel) * treeLayoutLinkDistance();
|
||||||
|
if (dist == 0) {
|
||||||
|
// nodes with depth in brackets: A (0) -> B (1) + C (2)
|
||||||
|
// then there is a pseudo node that is on the same level as B
|
||||||
|
dist = 0.5 * treeLayoutLinkDistance();
|
||||||
|
}
|
||||||
|
return dist;
|
||||||
|
})
|
||||||
|
.linkStrength(function(d) {
|
||||||
|
return strength = 1 / d.siblings;
|
||||||
|
})
|
||||||
|
.size([width, height]);
|
||||||
|
|
||||||
|
force.nodes(graph.nodes).links(graph.links).start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawPathway(elem, options) {
|
||||||
|
|
||||||
|
d3.json("", function(error, graph) {
|
||||||
|
if (graph) {
|
||||||
|
_drawPathway(elem, graph, options);
|
||||||
|
} else {
|
||||||
|
throw new Error("Graph not defined: " + error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
228
static/js/jquery-bootstrap-modal-steps.js
vendored
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
/* global jQuery */
|
||||||
|
|
||||||
|
(function($){
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
$.fn.modalSteps = function(options){
|
||||||
|
var $modal = this;
|
||||||
|
|
||||||
|
var settings = $.extend({
|
||||||
|
btnCancelHtml: 'Cancel',
|
||||||
|
btnPreviousHtml: 'Previous',
|
||||||
|
btnNextHtml: 'Next',
|
||||||
|
btnLastStepHtml: 'Complete',
|
||||||
|
disableNextButton: false,
|
||||||
|
completeCallback: function(){},
|
||||||
|
callbacks: {}
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
|
||||||
|
var validCallbacks = function(){
|
||||||
|
var everyStepCallback = settings.callbacks['*'];
|
||||||
|
|
||||||
|
if (everyStepCallback !== undefined && typeof(everyStepCallback) !== 'function'){
|
||||||
|
throw 'everyStepCallback is not a function! I need a function';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(settings.completeCallback) !== 'function') {
|
||||||
|
throw 'completeCallback is not a function! I need a function';
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var step in settings.callbacks){
|
||||||
|
if (settings.callbacks.hasOwnProperty(step)){
|
||||||
|
var callback = settings.callbacks[step];
|
||||||
|
|
||||||
|
if (step !== '*' && callback !== undefined && typeof(callback) !== 'function'){
|
||||||
|
throw 'Step ' + step + ' callback must be a function';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var executeCallback = function(callback){
|
||||||
|
if (callback !== undefined && typeof(callback) === 'function'){
|
||||||
|
callback();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
$modal
|
||||||
|
.on('show.bs.modal', function(){
|
||||||
|
var $modalFooter = $modal.find('.modal-footer'),
|
||||||
|
$btnCancel = $modalFooter.find('.js-btn-step[data-orientation=cancel]'),
|
||||||
|
$btnPrevious = $modalFooter.find('.js-btn-step[data-orientation=previous]'),
|
||||||
|
$btnNext = $modalFooter.find('.js-btn-step[data-orientation=next]'),
|
||||||
|
everyStepCallback = settings.callbacks['*'],
|
||||||
|
stepCallback = settings.callbacks['1'],
|
||||||
|
actualStep,
|
||||||
|
$actualStep,
|
||||||
|
titleStep,
|
||||||
|
$titleStepSpan,
|
||||||
|
nextStep;
|
||||||
|
|
||||||
|
if (settings.disableNextButton){
|
||||||
|
$btnNext.attr('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
$btnPrevious.attr('disabled', 'disabled');
|
||||||
|
|
||||||
|
validCallbacks();
|
||||||
|
executeCallback(everyStepCallback);
|
||||||
|
executeCallback(stepCallback);
|
||||||
|
|
||||||
|
// Setting buttons
|
||||||
|
$btnCancel.html(settings.btnCancelHtml);
|
||||||
|
$btnPrevious.html(settings.btnPreviousHtml);
|
||||||
|
$btnNext.html(settings.btnNextHtml);
|
||||||
|
|
||||||
|
$actualStep = $('<input>').attr({
|
||||||
|
'type': 'hidden',
|
||||||
|
'id': 'actual-step',
|
||||||
|
'value': '1',
|
||||||
|
});
|
||||||
|
|
||||||
|
$modal.find('#actual-step').remove();
|
||||||
|
$modal.append($actualStep);
|
||||||
|
|
||||||
|
actualStep = 1;
|
||||||
|
nextStep = actualStep + 1;
|
||||||
|
|
||||||
|
$modal.find('[data-step=' + actualStep + ']').removeClass('hide');
|
||||||
|
$btnNext.attr('data-step', nextStep);
|
||||||
|
|
||||||
|
titleStep = $modal.find('[data-step=' + actualStep + ']').data('title');
|
||||||
|
$titleStepSpan = $('<span>')
|
||||||
|
.addClass('label label-success')
|
||||||
|
.html(actualStep);
|
||||||
|
|
||||||
|
$modal
|
||||||
|
.find('.js-title-step')
|
||||||
|
.append($titleStepSpan)
|
||||||
|
.append(' ' + titleStep);
|
||||||
|
})
|
||||||
|
.on('hidden.bs.modal', function(){
|
||||||
|
var $actualStep = $modal.find('#actual-step'),
|
||||||
|
$btnNext = $modal.find('.js-btn-step[data-orientation=next]');
|
||||||
|
|
||||||
|
$modal
|
||||||
|
.find('[data-step]')
|
||||||
|
.not($modal.find('.js-btn-step'))
|
||||||
|
.addClass('hide');
|
||||||
|
|
||||||
|
$actualStep
|
||||||
|
.not($modal.find('.js-btn-step'))
|
||||||
|
.remove();
|
||||||
|
|
||||||
|
$btnNext
|
||||||
|
.attr('data-step', 1)
|
||||||
|
.html(settings.btnNextHtml);
|
||||||
|
|
||||||
|
$modal.find('.js-title-step').html('');
|
||||||
|
});
|
||||||
|
|
||||||
|
$modal.find('.js-btn-step').on('click', function(){
|
||||||
|
var $btn = $(this),
|
||||||
|
$actualStep = $modal.find('#actual-step'),
|
||||||
|
$btnPrevious = $modal.find('.js-btn-step[data-orientation=previous]'),
|
||||||
|
$btnNext = $modal.find('.js-btn-step[data-orientation=next]'),
|
||||||
|
$title = $modal.find('.js-title-step'),
|
||||||
|
orientation = $btn.data('orientation'),
|
||||||
|
actualStep = parseInt($actualStep.val()),
|
||||||
|
everyStepCallback = settings.callbacks['*'],
|
||||||
|
steps,
|
||||||
|
nextStep,
|
||||||
|
$nextStep,
|
||||||
|
newTitle;
|
||||||
|
|
||||||
|
steps = $modal.find('div[data-step]').length;
|
||||||
|
|
||||||
|
// Callback on Complete
|
||||||
|
if ($btn.attr('data-step') === 'complete'){
|
||||||
|
settings.completeCallback();
|
||||||
|
$modal.modal('hide');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the orientation to make logical operations with actualStep/nextStep
|
||||||
|
if (orientation === 'next'){
|
||||||
|
nextStep = actualStep + 1;
|
||||||
|
|
||||||
|
$btnPrevious.attr('data-step', actualStep);
|
||||||
|
$actualStep.val(nextStep);
|
||||||
|
|
||||||
|
} else if (orientation === 'previous'){
|
||||||
|
nextStep = actualStep - 1;
|
||||||
|
|
||||||
|
$btnNext.attr('data-step', actualStep);
|
||||||
|
$btnPrevious.attr('data-step', nextStep - 1);
|
||||||
|
|
||||||
|
$actualStep.val(actualStep - 1);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$modal.modal('hide');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt($actualStep.val()) === steps){
|
||||||
|
$btnNext
|
||||||
|
.attr('data-step', 'complete')
|
||||||
|
.html(settings.btnLastStepHtml);
|
||||||
|
} else {
|
||||||
|
$btnNext
|
||||||
|
.attr('data-step', nextStep)
|
||||||
|
.html(settings.btnNextHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.disableNextButton){
|
||||||
|
$btnNext.attr('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide and Show steps
|
||||||
|
$modal
|
||||||
|
.find('[data-step=' + actualStep + ']')
|
||||||
|
.not($modal.find('.js-btn-step'))
|
||||||
|
.addClass('hide');
|
||||||
|
|
||||||
|
$modal
|
||||||
|
.find('[data-step=' + nextStep + ']')
|
||||||
|
.not($modal.find('.js-btn-step'))
|
||||||
|
.removeClass('hide');
|
||||||
|
|
||||||
|
// Just a check for the class of previous button
|
||||||
|
if (parseInt($btnPrevious.attr('data-step')) > 0 ){
|
||||||
|
$btnPrevious.removeAttr('disabled');
|
||||||
|
} else {
|
||||||
|
$btnPrevious.attr('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orientation === 'previous'){
|
||||||
|
$btnNext.removeAttr('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next step
|
||||||
|
$nextStep = $modal.find('[data-step=' + nextStep + ']');
|
||||||
|
|
||||||
|
// Verify if we need to unlock continue btn of the next step
|
||||||
|
if ($nextStep.attr('data-unlock-continue')){
|
||||||
|
$btnNext.removeAttr('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the title of step
|
||||||
|
newTitle = $nextStep.attr('data-title');
|
||||||
|
var $titleStepSpan = $('<span>')
|
||||||
|
.addClass('label label-success')
|
||||||
|
.html(nextStep);
|
||||||
|
|
||||||
|
$title
|
||||||
|
.html($titleStepSpan)
|
||||||
|
.append(' ' + newTitle);
|
||||||
|
|
||||||
|
var stepCallback = settings.callbacks[$actualStep.val()];
|
||||||
|
executeCallback(everyStepCallback);
|
||||||
|
executeCallback(stepCallback);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
}(jQuery));
|
||||||
57
static/js/ketcher2/DEVNOTES.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Stable [Node.js](https://nodejs.org) version
|
||||||
|
|
||||||
|
## Build instructions
|
||||||
|
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
|
||||||
|
For production build:
|
||||||
|
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
You could also build only the style with command
|
||||||
|
|
||||||
|
npm run style
|
||||||
|
|
||||||
|
## Indigo Service
|
||||||
|
|
||||||
|
Ketcher uses Indigo Service for server operations.
|
||||||
|
You can use `--api-path` parameter to start with it:
|
||||||
|
|
||||||
|
npm start -- --api-path=<server-url>
|
||||||
|
For production build:
|
||||||
|
|
||||||
|
npm run build -- --api-path=<server-url>
|
||||||
|
|
||||||
|
You can find the instruction for service installation
|
||||||
|
[here](http://lifescience.opensource.epam.com/indigo/service/index.html).
|
||||||
|
|
||||||
|
## Tests instructions
|
||||||
|
|
||||||
|
You can start tests for input/output `.mol`-files and render.
|
||||||
|
|
||||||
|
npm test
|
||||||
|
|
||||||
|
Tests are started for all structures in `test/fixtures` directory.
|
||||||
|
|
||||||
|
To start the tests separately:
|
||||||
|
|
||||||
|
npm run test-io
|
||||||
|
npm run test-render
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
You can use following parameters to start the tests:
|
||||||
|
- `--fixtures` - for the choice of a specific directory with molecules
|
||||||
|
- `--headless` - for start of the browser in headless mode
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run test-render -- --fixtures=fixtures/super --headless
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have added new structures for testing to the `test/fixtures` directory
|
||||||
|
you have to generate `svg` from them for correct render-test with:
|
||||||
|
|
||||||
|
npm run generate-svg
|
||||||
184
static/js/ketcher2/LICENSE
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2017 EPAM Systems
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
5
static/js/ketcher2/LICENSE-history
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Ketcher version 1 was released under GNU Affero General Public License v3.0
|
||||||
|
Ketcher version 2 was re-licensed under Apache License, Version 2.
|
||||||
|
|
||||||
|
Current version is distributed by the terms of the Apache License, Version 2.
|
||||||
|
which is included in the file LICENSE, found at the root of the Ketcher source tree.
|
||||||
19
static/js/ketcher2/NOTICE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Ketcher
|
||||||
|
Copyright (C) 2017 EPAM Systems
|
||||||
|
This product includes software developed at EPAM Systems, Inc.
|
||||||
|
|
||||||
|
In addition, this product contains dependencies on files licensed under:
|
||||||
|
|
||||||
|
The FreeBSD Documentation License https://www.freebsd.org/copyright/freebsd-doc-license.html
|
||||||
|
The MIT License https://opensource.org/licenses/MIT
|
||||||
|
X11 License http://www.xfree86.org/3.3.6/COPYRIGHT2.html
|
||||||
|
Academic Free License https://opensource.org/licenses/AFL-3.0
|
||||||
|
Apache License, Version 1.0 http://www.apache.org/licenses/LICENSE-1.0
|
||||||
|
Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
The 2-Clause BSD License https://opensource.org/licenses/BSD-2-Clause
|
||||||
|
The 3-Clause BSD License https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
ISC License (ISC) https://opensource.org/licenses/ISC
|
||||||
|
GNU Lesser General Public License version 2.1 https://opensource.org/licenses/LGPL-2.1
|
||||||
|
The Mozilla Public License https://opensource.org/licenses/MPL-1.0
|
||||||
|
Public Domain https://wiki.creativecommons.org/wiki/Public_domain
|
||||||
|
Unlicense http://unlicense.org/
|
||||||
35
static/js/ketcher2/README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# EPAM Ketcher projects
|
||||||
|
Copyright (c) 2017 EPAM Systems, Inc
|
||||||
|
|
||||||
|
Ketcher is an open-source web-based chemical structure editor incorporating high performance, good portability, light weight, and ability to easily integrate into a custom web-application. Ketcher is designed for chemists, laboratory scientists and technicians who draw structures and reactions.
|
||||||
|
|
||||||
|
## KEY FEATURES
|
||||||
|
* Fast 2D structure representation that satisfies common chemical drawing standards
|
||||||
|
* 3D structure visualization
|
||||||
|
* Draw and edit structures using major tools: Atom Tool, Bond Tool, and Template Tool
|
||||||
|
* Template library (including custom and user's templates)
|
||||||
|
* Add atom and bond basic properties and query features, add aliases and Generic groups
|
||||||
|
* Select, modify, and erase connected and unconnected atoms and bonds using Selection Tool, or using Shift key
|
||||||
|
* Simple Structure Clean up Tool (checks bonds length, angles and spatial arrangement of atoms) and Advanced Structure Clean up Tool (+ stereochemistry checking and structure layout)
|
||||||
|
* Aromatize/De-aromatize Tool
|
||||||
|
* Calculate CIP Descriptors Tool
|
||||||
|
* Structure Check Tool
|
||||||
|
* MW and Structure Parameters Calculate Tool
|
||||||
|
* Stereochemistry support during editing, loading, and saving chemical structures
|
||||||
|
* Storing history of actions, with the ability to rollback to previous state
|
||||||
|
* Ability to load and save structures and reactions in MDL Molfile or RXN file format, InChI String, ChemAxon Extended SMILES, ChemAxon Extended CML file formats
|
||||||
|
* Easy to use R-Group and S-Group tools (Generic, Multiple group, SRU polymer, peratom, Data S-Group)
|
||||||
|
* Reaction Tool (reaction generating, manual and automatic atom-to-atom mapping)
|
||||||
|
* Flip/Rotate Tool
|
||||||
|
* Zoom in/out, hotkeys, cut/copy/paste
|
||||||
|
* OCR - ability to recognize structures at pictures (image files) and reproduce them
|
||||||
|
* Copy and paste between different chemical editors
|
||||||
|
* Settings support (Rendering, Displaying, Debugging)
|
||||||
|
* Use of SVG to achieve best quality in-browser chemical structure rendering
|
||||||
|
* Languages: JavaScript with third-party libraries
|
||||||
|
|
||||||
|
## Build instructions
|
||||||
|
Please read [DEVNOTES.md](DEVNOTES.md) for details.
|
||||||
|
|
||||||
|
## License
|
||||||
|
Please read [LICENSE](LICENSE) and [NOTICE](NOTICE) for details.
|
||||||
BIN
static/js/ketcher2/doc/analyse.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
static/js/ketcher2/doc/atom-dialog.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
static/js/ketcher2/doc/attpoints-dialog.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
static/js/ketcher2/doc/bond-dialog.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
static/js/ketcher2/doc/bond-types.png
Normal file
|
After Width: | Height: | Size: 1014 B |
BIN
static/js/ketcher2/doc/bond.png
Normal file
|
After Width: | Height: | Size: 630 B |
BIN
static/js/ketcher2/doc/bonds.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/js/ketcher2/doc/chain.png
Normal file
|
After Width: | Height: | Size: 430 B |
BIN
static/js/ketcher2/doc/charge.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
static/js/ketcher2/doc/check.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
static/js/ketcher2/doc/collapsed.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
static/js/ketcher2/doc/expanded.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
static/js/ketcher2/doc/generic-groups.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
570
static/js/ketcher2/doc/help.md
Normal file
@ -0,0 +1,570 @@
|
|||||||
|
**Ketcher** is a tool to draw molecular structures and chemical
|
||||||
|
reactions.
|
||||||
|
|
||||||
|
# Ketcher Overview
|
||||||
|
|
||||||
|
**Ketcher** is a tool to draw molecular structures and chemical
|
||||||
|
reactions. Ketcher operates in two modes, the Server mode with most
|
||||||
|
functions available and the client mode with limited functions
|
||||||
|
available.
|
||||||
|
|
||||||
|
**Ketcher** consists of the following elements:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Note** : Depending on the screen size, some tools on the _Tool
|
||||||
|
palette_ can be displayed in expanded or collapsed forms.
|
||||||
|
|
||||||
|
Using the _Tool palette_, you can
|
||||||
|
|
||||||
|
* draw and edit a molecule or reaction by clicking on and dragging
|
||||||
|
atoms, bonds, and other elements provided with the buttons on the
|
||||||
|
_Atoms_ toolbar and _Tool palette_;
|
||||||
|
|
||||||
|
* delete any element of the drawing (atom or bond) by clicking on it
|
||||||
|
with the Erase tool;
|
||||||
|
|
||||||
|
* delete the entire molecule or its fragment by a lasso,
|
||||||
|
rectangular, or fragment selection with the Erase tool;
|
||||||
|
|
||||||
|
* draw special structures (see the following sections);
|
||||||
|
|
||||||
|
* select the entire molecule or its fragment in one of the following
|
||||||
|
ways (click on the button to see the list of available options):
|
||||||
|
|
||||||
|
* in the expanded form
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* in the collapsed form
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
To select one atom or bond, click Lasso or Rectangle Selection tool,
|
||||||
|
and then click the atom or bond.
|
||||||
|
|
||||||
|
To select the entire structure:
|
||||||
|
|
||||||
|
* Select the Fragment Selection tool and then click the object.
|
||||||
|
|
||||||
|
* Select the Lasso or Rectangle Selection tool, and then drag the
|
||||||
|
mouse to select the object.
|
||||||
|
|
||||||
|
* `Ctrl-click` with the Lasso or Rectangle Selection tool.
|
||||||
|
|
||||||
|
To select multiple atoms, bonds, structures, or other objects, do one
|
||||||
|
of the following:
|
||||||
|
|
||||||
|
* `Shift-click` with the Lasso or Rectangle Selection tool selects
|
||||||
|
some (connected or not) atoms/bonds.
|
||||||
|
|
||||||
|
* With the Lasso or Rectangle Selection tool click and drag the
|
||||||
|
mouse around the atoms, bonds, or structures that you want to
|
||||||
|
select.
|
||||||
|
|
||||||
|
**Note** : `Ctrl+Shift-click` with the Lasso or Rectangle Selection tool
|
||||||
|
selects several structures.
|
||||||
|
|
||||||
|
You can use the buttons of the _Main_ toolbar:
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* **Clear Canvas** (1) button to start drawing a new molecule; this
|
||||||
|
command clears the drawing area;
|
||||||
|
|
||||||
|
* **Open…** (2) and **Save As…** (3) buttons to import a molecule
|
||||||
|
from a molecular file or save it to a supported molecular file
|
||||||
|
format;
|
||||||
|
|
||||||
|
* **Undo** / **Redo** (4), **Cut** (5), **Copy** (6), **Paste** (7),
|
||||||
|
**Zoom In** / **Out** (8), and **Scaling** (9) buttons to perform
|
||||||
|
the corresponding actions;
|
||||||
|
|
||||||
|
* **Layout** button (10) to change the position of the structure to
|
||||||
|
work with it with the most convenience;
|
||||||
|
|
||||||
|
* **Clean Up** button (11) to improve the appearance of the
|
||||||
|
structure by assigning them uniform bond lengths and angles.
|
||||||
|
|
||||||
|
* **Aromatize** / **Dearomatize** buttons (12) to mark aromatic
|
||||||
|
structures (to convert a structure to the Aromatic or Kekule
|
||||||
|
presentation);
|
||||||
|
|
||||||
|
* **Calculate CIP** button (13) to determine R/S and E/Z
|
||||||
|
configurations;
|
||||||
|
|
||||||
|
* **Check Structure** button (14) to check the following properties
|
||||||
|
of the structure:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* **Calculated Values** button (15) to display some properties of
|
||||||
|
the structure:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* **Recognize Molecule** button (16) to recognize a structure in the
|
||||||
|
image file and load it to the canvas;
|
||||||
|
|
||||||
|
* **3D Viewer** button (17) to open the structure in the
|
||||||
|
three-dimensional Viewer;
|
||||||
|
|
||||||
|
* **Settings** button (18) to make some settings for molecular
|
||||||
|
files:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* **Help** button (19) to view Help;
|
||||||
|
|
||||||
|
* **About** button (20) to display version and copyright information
|
||||||
|
of the program.
|
||||||
|
|
||||||
|
**Note** : **Layout,** **Clean Up,** **Aromatize** / **Dearomatize,**
|
||||||
|
**Calculate CIP,** **Check Structure,** **Calculated Values,**
|
||||||
|
**Recognize Molecule** and **3D View** buttons are active only in the
|
||||||
|
Server mode.
|
||||||
|
|
||||||
|
# 3D Viewer
|
||||||
|
|
||||||
|
The structure appears in a modal window after clicking on the **3D
|
||||||
|
Viewer** button:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
You can perform the following actions:
|
||||||
|
|
||||||
|
* Rotate the structure holding the left mouse button;
|
||||||
|
|
||||||
|
* Zoom In/Out the structure;
|
||||||
|
|
||||||
|
Ketcher Settings allow to change the appearance of the structure and background coloring.
|
||||||
|
|
||||||
|
"Lines" drawing method, "Bright" atom name coloring
|
||||||
|
method and "Light" background coloring are default.
|
||||||
|
|
||||||
|
# Drawing Atoms
|
||||||
|
|
||||||
|
To draw/edit atoms you can:
|
||||||
|
|
||||||
|
* select an atom in the Atoms toolbar and click inside the drawing
|
||||||
|
area;
|
||||||
|
|
||||||
|
* if the desired atom is absent in the toolbar, click on
|
||||||
|
the  button to invoke the Periodic Table and
|
||||||
|
click on the desired atom (available options: _Single_ – selection
|
||||||
|
of a single atom, _List_ – choose an atom from the list of selected
|
||||||
|
options (To allow one atom from a list of atoms of your choice at
|
||||||
|
that position), _Not List_ - exclude any atom on your list at that
|
||||||
|
position).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* add an atom to the existing molecule by selecting an atom in the
|
||||||
|
_Atoms_ toolbar, clicking on an atom in the molecule, and dragging
|
||||||
|
the cursor; the atom will be added with a single bond; vacant
|
||||||
|
valences will be filled with the corresponding number of hydrogen
|
||||||
|
atoms;
|
||||||
|
|
||||||
|
* change an atom by selecting an atom in the _Atoms_ toolbar and
|
||||||
|
clicking on the atom to be changed; in the case a wrong valence thus
|
||||||
|
appears the atom will be underlined in red;
|
||||||
|
|
||||||
|
* change an atom by clicking on an existing atom with the
|
||||||
|
_Selection_ tool and waiting for a couple of seconds for the text
|
||||||
|
box to appear; type another atom symbol in the text box:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* change the charge of an atom by selecting the Charge Plus or
|
||||||
|
Charge Minus tool and clicking consecutively on an atom to
|
||||||
|
increase/decrease its charge
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* change an atom or its properties by double-clicking on the atom to
|
||||||
|
invoke the Atom Properties dialog (the dialog also provides atom
|
||||||
|
query features):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* click on the Periodic Table button, open the Extended table and
|
||||||
|
select a corresponding Generic group or Special Node:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Drawing Bonds
|
||||||
|
|
||||||
|
To draw/edit bonds you can:
|
||||||
|
|
||||||
|
* Click an arrow on the Bond tool  in the Tools palette
|
||||||
|
to open the drop-down list with the following bond types:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
For the full screen format, the Bond tool from the Tools palette
|
||||||
|
splits into three: _Single Bond,__Single Up Bond,_ and _Any
|
||||||
|
Bond_,which include the corresponding bond types:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* select a bond type from the drop down list and click inside the
|
||||||
|
drawing area; a bond of the selected type will be drawn;
|
||||||
|
|
||||||
|
* click on an atom in the molecule; a bond of the selected type will
|
||||||
|
be added to the atom at the angle of 120 degrees;
|
||||||
|
|
||||||
|
* add a bond to the existing molecule by clicking on an atom in the
|
||||||
|
molecule and dragging the cursor; in this case you can set the angle
|
||||||
|
manually;
|
||||||
|
|
||||||
|
* change the bond type by clicking on it;
|
||||||
|
|
||||||
|
* use the Chain Tool  to draw consecutive single
|
||||||
|
bonds;
|
||||||
|
|
||||||
|
* change a bond or its properties by double-clicking on the bond to
|
||||||
|
invoke the Bond Properties dialog:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* clicking on a drawn stereo bond changes its direction.
|
||||||
|
|
||||||
|
* clicking with the Single Bond tool or Chain tool switches the bond type
|
||||||
|
cyclically: Single-Double-Triple-Single.
|
||||||
|
|
||||||
|
# Drawing R-Groups
|
||||||
|
|
||||||
|
Use the _R-Group_ toolbox  to draw R-groups in Markush
|
||||||
|
structures:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Selecting the _R-Group_ _Label_ Tool and clicking on an atom in the
|
||||||
|
structure invokes the dialog to select the R-Group label for a current
|
||||||
|
atom position in the structure:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Selecting the R-Group label and clicking **OK** converts the structure
|
||||||
|
into a Markush structure with the selected R-Group label:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Note** : You can choose several R-Group labels simultaneously:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Particular chemical fragments that may be substituted for a given
|
||||||
|
R-Group form a set of R-Group members. R-Group members can be any
|
||||||
|
structural fragment, including functional groups and single atoms or
|
||||||
|
atom lists.
|
||||||
|
|
||||||
|
To create a set of R-Group members:
|
||||||
|
|
||||||
|
1. Draw a structure to become an R-Group member.
|
||||||
|
|
||||||
|
2. Select the structure using the _R-Group Fragment Tool_ to invoke
|
||||||
|
the R-Group dialog; in this dialog select the label of the
|
||||||
|
R-Group to assign the fragment to.
|
||||||
|
|
||||||
|
3. Click on **OK** to convert the structure into an R-Group member.
|
||||||
|
|
||||||
|
An R-Group attachment point is the atom in an R-Group member fragment
|
||||||
|
that attaches the fragment to the initial Markush structure.
|
||||||
|
|
||||||
|
Selecting the _Attachment Point Tool_ and clicking on an atom in the
|
||||||
|
R-Group fragment converts this atom into an attachment point. If the
|
||||||
|
R-Group contains more than one attachment point, you can specify one
|
||||||
|
of them as primary and the other as secondary. You can select between
|
||||||
|
either the primary or secondary attachment point using the dialog that
|
||||||
|
appears after clicking on the atom:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
If there are two attachment points on an R-Group member, there must be
|
||||||
|
two corresponding attachments (bonds) to the R-Group atom that has the
|
||||||
|
same R-Group label. Clicking on **OK** in the above dialog creates the
|
||||||
|
attachment point.
|
||||||
|
|
||||||
|
Schematically, the entire process of the R-Group member creation can
|
||||||
|
be presented as:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# R-Group Logic
|
||||||
|
|
||||||
|
**Ketcher** enables one to add logic when using R-Groups. To access
|
||||||
|
the R-Group logic:
|
||||||
|
|
||||||
|
1. Create an R-Group member fragment as described above.
|
||||||
|
|
||||||
|
2. Move the cursor over the entire fragment for the green frame to
|
||||||
|
appear, then click inside the fragment. The following dialog
|
||||||
|
appears:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. Specify **Occurrence** to define how many of an R-Group
|
||||||
|
occurs. If an R-Group atom appears several times in the initial
|
||||||
|
structure, you will specify **Occurrence**">n", n
|
||||||
|
being the number of occurrences; if it appears once, you see
|
||||||
|
"R1 > 0".
|
||||||
|
|
||||||
|
4. Specify H at **unoccupied** R-Group sites ( **RestH** ): check or
|
||||||
|
clear the checkbox.
|
||||||
|
|
||||||
|
5. Specify the logical **Condition**. Use the R-Group condition **If
|
||||||
|
R(i) Then** to specify whether the presence of an R-Group is
|
||||||
|
dependent on the presence of another R-Group.
|
||||||
|
|
||||||
|
|
||||||
|
# Marking S-Groups
|
||||||
|
|
||||||
|
To mark S-Groups, use the _S-Group tool_  and the
|
||||||
|
following dialog that appears after selecting a fragment with this
|
||||||
|
tool:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Available S-Group types:
|
||||||
|
|
||||||
|
_Generic_
|
||||||
|
|
||||||
|
Generic is a pair of brackets without any labels.
|
||||||
|
|
||||||
|
_Multiple group_
|
||||||
|
|
||||||
|
A Multiple group indicates a number of replications of a fragment or a part of a
|
||||||
|
structure in contracted form.
|
||||||
|
|
||||||
|
_SRU Polymer_
|
||||||
|
|
||||||
|
The Structural Repeating Unit (SRU) brackets enclose the structural
|
||||||
|
repeating of a polymer. You have three available patterns:
|
||||||
|
head-to-tail (the default), head-to-head, and either/unknown.
|
||||||
|
|
||||||
|
_Superatom_
|
||||||
|
|
||||||
|
An abbreviated structure (abbreviation) is all or part of a structure
|
||||||
|
(molecule or reaction component) that has been abbreviated to a text
|
||||||
|
label. Structures that you abbreviate keep their chemical
|
||||||
|
significance, but their underlying structure is hidden. The current
|
||||||
|
version can't display contracted structures but correctly
|
||||||
|
saves/reads them into/from files.
|
||||||
|
|
||||||
|
# Data S-Groups
|
||||||
|
|
||||||
|
The _Data S-Groups Tool_  is a separate tool for
|
||||||
|
comfortable use with the accustomed set of descriptors (like Attached
|
||||||
|
Data in **Marvin** Editor).
|
||||||
|
|
||||||
|
You can attach data to an atom, a fragment, a single bond, or a
|
||||||
|
group. The defined set of _Names_ and _Values_ is introduced for each
|
||||||
|
type of selected elements:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* Select the appropriate S-Group Field Name.
|
||||||
|
|
||||||
|
* Select or type the appropriate Field Value.
|
||||||
|
|
||||||
|
* Labels can be specified as Absolute, Relative or Attached.
|
||||||
|
|
||||||
|
# Changing Structure Display
|
||||||
|
|
||||||
|
Use the _Flip/Rotate_ tool  to change the structure
|
||||||
|
display:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
For the full screen format, the _Flip/Rotate_ tool is split into
|
||||||
|
separate buttons:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
_Rotate Tool_
|
||||||
|
|
||||||
|
This tool allows rotating objects.
|
||||||
|
* If some objects are selected, the tool rotates the selected objects.
|
||||||
|
* If no objects are selected, or all objects are selected, the tool rotates the whole canvas
|
||||||
|
* The default rotation step is 15 degrees.
|
||||||
|
* Press and hold the Ctrl key for more gradual continuous rotation with 1 degree rotation step
|
||||||
|
|
||||||
|
Select any bond on the structure and click Alt+H to rotate the structure so that the selected bond is placed horizontally.
|
||||||
|
Select any bond on the structure and click Alt+V to rotate the structure so that the selected bond is placed vertically.
|
||||||
|
|
||||||
|
_Flip Tool_
|
||||||
|
|
||||||
|
This tool flips the objects horizontally or vertically.
|
||||||
|
* If some objects are selected, the Horizontal Flip tool (or Alt+H) flips the selected objects horizontally
|
||||||
|
* If no objects are selected, or all objects are selected, the Horizontal Flip tool (or Alt+H) flips each structure horizontally
|
||||||
|
* If some objects are selected, the Vertical Flip tool (or Alt+V) flips the selected objects vertically
|
||||||
|
* If no objects are selected, or all objects are selected, the Vertical Flip tool (or Alt+V) flips each structure vertically
|
||||||
|
|
||||||
|
# Drawing Reactions
|
||||||
|
|
||||||
|
To draw/edit reactions you can
|
||||||
|
|
||||||
|
* draw reagents and products as described above;
|
||||||
|
* use options of the _Reaction Arrow Tool_  to draw an
|
||||||
|
arrow and pluses in the reaction equation and map same atoms in
|
||||||
|
reagents and products.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Note** : Reaction Auto-Mapping Tool is available only in the Server
|
||||||
|
mode.
|
||||||
|
|
||||||
|
# Templates toolbar
|
||||||
|
|
||||||
|
You can add templates (rings or other predefined structures) to the
|
||||||
|
structure using the _Templates_ toolbar together with the _Custom
|
||||||
|
Templates_ button located at the bottom:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
To add a ring to the molecule, select a ring from the toolbar and
|
||||||
|
click inside the drawing area, or click on an atom or a bond in the
|
||||||
|
molecule.
|
||||||
|
|
||||||
|
Rules of using templates:
|
||||||
|
|
||||||
|
* Selecting a template and clicking on an atom in the existing
|
||||||
|
structure adds the template to the structure connected with a single
|
||||||
|
bond:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* Selecting a template and dragging the cursor from an atom in the
|
||||||
|
existing structure adds the template directly to this atom resulting
|
||||||
|
in the fused structure:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* Dragging the cursor from an atom in the existing structure results
|
||||||
|
in the single bond attachment if the cursor is dragged to more than
|
||||||
|
the bond length; otherwise the fused structure is drawn.
|
||||||
|
* Selecting a template and clicking on a bond in the existing
|
||||||
|
structure created a bond-to-bond fused structure:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* The bond in the initial structure is replaced with the bond in the
|
||||||
|
template.
|
||||||
|
|
||||||
|
* This procedure doesn't change the length of the bond in the
|
||||||
|
initial structure.
|
||||||
|
|
||||||
|
* Dragging the cursor relative to the initial bond applies the
|
||||||
|
template at the corresponding side of the bond.
|
||||||
|
|
||||||
|
**Note** : The added template will be fused by the default attachment
|
||||||
|
atom or bond preset in the program.
|
||||||
|
|
||||||
|
**Note** : User is able to define the attachment atom and bond by clicking
|
||||||
|
the Edit button for template structure.
|
||||||
|
|
||||||
|
|
||||||
|
The _Custom Templates_ button invokes the scrolling
|
||||||
|
list of templates available in the program; both built-in and created
|
||||||
|
by user:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
To create a user template:
|
||||||
|
* draw a structure.
|
||||||
|
* click the Save as button.
|
||||||
|
* click the Save to Templates button.
|
||||||
|
* enter a name and define the attachment atom and bond.
|
||||||
|
|
||||||
|
# Working with Files
|
||||||
|
|
||||||
|
Ketcher supports the following molecular formats that can be entered
|
||||||
|
either manually or from files:
|
||||||
|
|
||||||
|
* MDL Molfile or RXN file;
|
||||||
|
|
||||||
|
* Daylight SMILES (Server mode only);
|
||||||
|
|
||||||
|
* Daylight SMARTS (Server mode only);
|
||||||
|
|
||||||
|
* InChi string (Server mode only);
|
||||||
|
|
||||||
|
* CML file (Server mode only).
|
||||||
|
|
||||||
|
You can use the **Open…** and **Save As…** buttons of the _Main_
|
||||||
|
toolbar to import a molecule from a molecular file or save it to a
|
||||||
|
supported molecular file format. The _Open Structure_ dialog enables
|
||||||
|
one to either browse for a file (Server mode) or manually input, e.g.,
|
||||||
|
the Molfile ctable for the molecule to be imported:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The _Save Structure_ dialog enables one to save the molecular file:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Note** : In the standalone version only mol/rxn are supported for
|
||||||
|
Open and mol/rxn/SMILES for Save.
|
||||||
|
|
||||||
|
|
||||||
|
# Hotkeys
|
||||||
|
|
||||||
|
You can use keyboard hotkeys (including Numeric keypad) for some
|
||||||
|
features/commands of the Editor. To display the hotkeys just place the
|
||||||
|
cursor over a toolbar button. If a hotkey is available for the button,
|
||||||
|
it will appear in brackets after the description of the button.
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
| --- | --- |
|
||||||
|
| `Esc` | Switching between the Lasso/Rectangle/Fragment Selection tools |
|
||||||
|
| `Del` | Delete the selected objects |
|
||||||
|
| `0` | Draw Any bond. |
|
||||||
|
| `1` | Single / Single Up / Single Down / Single Up/Down bond. Consecutive pressing switches between these types. |
|
||||||
|
| `2` | Double / Double Cis/Trans bond |
|
||||||
|
| `3` | Draw a triple bond. |
|
||||||
|
| `4` | Draw an aromatic bond. |
|
||||||
|
| `5` | Charge Plus/Charge Minus |
|
||||||
|
| `A` | Draw any atom |
|
||||||
|
| `H` | Draw a hydrogen |
|
||||||
|
| `C` | Draw a carbon |
|
||||||
|
| `N` | Draw a nitrogen |
|
||||||
|
| `O` | Draw an oxygen |
|
||||||
|
| `S` | Draw a sulfur |
|
||||||
|
| `F` | Draw a fluorine |
|
||||||
|
| `P` | Draw a phosphorus |
|
||||||
|
| `I` | Draw an iodine |
|
||||||
|
| `T` | Basic templates. Consecutive pressing switches between different templates |
|
||||||
|
| `Shift+t` | Open template library |
|
||||||
|
| `Alt+r` | Rotate tool |
|
||||||
|
| `Alt+v` | Flip vertically |
|
||||||
|
| `Alt+h` | Flip horizontally |
|
||||||
|
| `Ctrl+g` | S-Group tool / Data S-Group tool |
|
||||||
|
| `Ctrl+d` | Align and select all S-Group data
|
||||||
|
| `Ctrl+r` | Switching between the R-Group Label Tool/R-Group Fragment Tool/Attachment Point Tool |
|
||||||
|
| `Ctrl+Shift+r` | R-Group Fragment Tool |
|
||||||
|
| `Ctrl+Del` | Clear canvas |
|
||||||
|
| `Ctrl+o` | Open |
|
||||||
|
| `Ctrl+s` | Save As |
|
||||||
|
| `Ctrl+z` | Undo |
|
||||||
|
| `Ctrl+Shift+z` | Redo |
|
||||||
|
| `Ctrl+x` | Cut selected objects |
|
||||||
|
| `Ctrl+c` | Copy selected objects |
|
||||||
|
| `Ctrl+v` | Paste selected objects |
|
||||||
|
| `+` | Zoom In |
|
||||||
|
| `-` | Zoom Out |
|
||||||
|
| `Ctrl+l` | Layout |
|
||||||
|
| `Ctrl+Shift+l` | Clean Up |
|
||||||
|
| `Ctrl+p` | Calculate CIP |
|
||||||
|
| `?` | Help |
|
||||||
|
|
||||||
|
**Note** : Please, use `Ctrl+V` to paste the selected object in
|
||||||
|
Google Chrome and Mozilla Firefox browsers.
|
||||||
|
|
||||||
|
**Note 2** : Probably, you have forbidden access to the local storage.
|
||||||
|
If you are using IE10 or IE11 and didn't forbid access to local storage
|
||||||
|
intentionally, you can pay attention here: https://stackoverflow.com/a/20848924
|
||||||
BIN
static/js/ketcher2/doc/inline-edit.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/js/ketcher2/doc/main.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
static/js/ketcher2/doc/miew-menu.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
static/js/ketcher2/doc/miew.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
static/js/ketcher2/doc/open.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
static/js/ketcher2/doc/periodic-dialog-ext.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
static/js/ketcher2/doc/periodic-dialog.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
static/js/ketcher2/doc/periodic-table.png
Normal file
|
After Width: | Height: | Size: 903 B |
BIN
static/js/ketcher2/doc/reaction-types.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
static/js/ketcher2/doc/reaction.png
Normal file
|
After Width: | Height: | Size: 887 B |
BIN
static/js/ketcher2/doc/rgroup-dialog.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
static/js/ketcher2/doc/rgroup-example1.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
static/js/ketcher2/doc/rgroup-example2.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
static/js/ketcher2/doc/rgroup-example3.png
Normal file
|
After Width: | Height: | Size: 10 KiB |