forked from enviPath/enviPy
Compare commits
28 Commits
beta_2025-
...
feature/en
| Author | SHA1 | Date | |
|---|---|---|---|
| 04f9c9252a | |||
| c2d45917ce | |||
| 13ed86a780 | |||
| f1b4c5aadb | |||
| 37e0e18a28 | |||
| de44c22606 | |||
| a952c08469 | |||
| 551cfc7768 | |||
| 8fda2577ee | |||
| 819a94aced | |||
| 376fd65785 | |||
| d5ebb23622 | |||
| 93dd811e39 | |||
| 9a4735246f | |||
| 1f863fdcd6 | |||
| 1effaeb342 | |||
| 386098b8a6 | |||
| ef697ac5f5 | |||
| 68a3f3b982 | |||
| afeb56622c | |||
| 22f0bbe10b | |||
| 36879c266b | |||
| c2c46fbfa7 | |||
| d2f4fdc58a | |||
| 3f2b046bd6 | |||
| 7ad4112343 | |||
| 3f5bb76633 | |||
| b757a07f91 |
22
.env.local.example
Normal file
22
.env.local.example
Normal file
@ -0,0 +1,22 @@
|
||||
# Django settings
|
||||
SECRET_KEY='a-secure-secret-key-for-development'
|
||||
DEBUG=True
|
||||
ALLOWED_HOSTS=*
|
||||
|
||||
# Database settings (using PostgreSQL for local development)
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_DB=envipath
|
||||
POSTGRES_SERVICE_NAME=localhost
|
||||
POSTGRES_PORT=5432
|
||||
|
||||
# Celery settings
|
||||
CELERY_BROKER_URL='redis://localhost:6379/0'
|
||||
CELERY_RESULT_BACKEND='redis://localhost:6379/0'
|
||||
FLAG_CELERY_PRESENT=False
|
||||
|
||||
# Other settings
|
||||
LOG_LEVEL='INFO'
|
||||
SERVER_URL='http://localhost:8000'
|
||||
PLUGINS_ENABLED=True
|
||||
EP_DATA_DIR='data'
|
||||
@ -16,4 +16,3 @@ POSTGRES_PORT=
|
||||
# MAIL
|
||||
EMAIL_HOST_USER=
|
||||
EMAIL_HOST_PASSWORD=
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -6,3 +6,7 @@ static/django_extensions/
|
||||
.env
|
||||
debug.log
|
||||
scratches/
|
||||
|
||||
data/
|
||||
|
||||
.DS_Store
|
||||
|
||||
30
.pre-commit-config.yaml
Normal file
30
.pre-commit-config.yaml
Normal file
@ -0,0 +1,30 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.13.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
types_or: [python, pyi]
|
||||
args: [--fix]
|
||||
# Run the formatter.
|
||||
- id: ruff-format
|
||||
types_or: [python, pyi]
|
||||
|
||||
# - repo: local
|
||||
# hooks:
|
||||
# - id: django-check
|
||||
# name: Run Django Check
|
||||
# entry: uv run python manage.py check
|
||||
# language: system
|
||||
# pass_filenames: false
|
||||
# types: [python]
|
||||
@ -1 +1 @@
|
||||
3.10
|
||||
3.12
|
||||
|
||||
89
README.md
89
README.md
@ -1,2 +1,91 @@
|
||||
# enviPy
|
||||
|
||||
## Local Development Setup
|
||||
|
||||
These instructions will guide you through setting up the project for local development.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.11 or later
|
||||
- [uv](https://github.com/astral-sh/uv) - A fast Python package installer and resolver.
|
||||
- **Docker and Docker Compose** - Required for running the PostgreSQL database.
|
||||
- Git
|
||||
|
||||
> **Note:** This application requires PostgreSQL, which uses `ArrayField`. Docker is the recommended way to run PostgreSQL locally.
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
This project uses `uv` to manage dependencies and `poe-the-poet` for task running. First, [install `uv` if you don't have it yet](https://docs.astral.sh/uv/guides/install-python/).
|
||||
|
||||
Then, sync the project dependencies. This will create a virtual environment in `.venv` and install all necessary packages, including `poe-the-poet`.
|
||||
|
||||
```bash
|
||||
uv sync --dev
|
||||
```
|
||||
|
||||
> **Note on RDkit:** If you have a different version of rdkit installed globally, the dependency installation may fail. If this happens, please uninstall the global version and run `uv sync` again.
|
||||
|
||||
### 2. Set Up Environment File
|
||||
|
||||
Copy the example environment file for local setup:
|
||||
|
||||
```bash
|
||||
cp .env.local.example .env
|
||||
```
|
||||
|
||||
This file contains the necessary environment variables for local development.
|
||||
|
||||
### 3. Quick Setup with Poe
|
||||
|
||||
The easiest way to set up the development environment is by using the `poe` task runner, which is executed via `uv run`.
|
||||
|
||||
```bash
|
||||
uv run poe setup
|
||||
```
|
||||
|
||||
This single command will:
|
||||
1. Start the PostgreSQL database using Docker Compose.
|
||||
2. Run database migrations.
|
||||
3. Bootstrap initial data (anonymous user, default packages, models).
|
||||
|
||||
After setup, start the development server:
|
||||
|
||||
```bash
|
||||
uv run poe dev
|
||||
```
|
||||
|
||||
The application will be available at `http://localhost:8000`.
|
||||
|
||||
#### Other useful Poe commands:
|
||||
|
||||
You can list all available commands by running `uv run poe --help`.
|
||||
|
||||
```bash
|
||||
uv run poe db-up # Start PostgreSQL only
|
||||
uv run poe db-down # Stop PostgreSQL
|
||||
uv run poe migrate # Run migrations only
|
||||
uv run poe bootstrap # Bootstrap data only
|
||||
uv run poe shell # Open the Django shell
|
||||
uv run poe clean # Remove database volumes (WARNING: destroys all data)
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
* **Docker Connection Error:** If you see an error like `open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified` (on Windows), it likely means your Docker Desktop application is not running. Please start Docker Desktop and try the command again.
|
||||
|
||||
* **SSH Keys for Git Dependencies:** Some dependencies are installed from private git repositories and require SSH authentication. Ensure your SSH keys are configured correctly for Git.
|
||||
* For a general guide, see [GitHub's official documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
|
||||
* **Windows Users:** If `uv sync` hangs while fetching git dependencies, you may need to explicitly configure Git to use the Windows OpenSSH client and use the `ssh-agent` to manage your key's passphrase.
|
||||
|
||||
1. **Point Git to the correct SSH executable:**
|
||||
```powershell
|
||||
git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"
|
||||
```
|
||||
2. **Enable and use the SSH agent:**
|
||||
```powershell
|
||||
# Run these commands in an administrator PowerShell
|
||||
Get-Service ssh-agent | Set-Service -StartupType Automatic -PassThru | Start-Service
|
||||
|
||||
# Add your key to the agent. It will prompt for the passphrase once.
|
||||
ssh-add
|
||||
```
|
||||
|
||||
20
docker-compose.dev.yml
Normal file
20
docker-compose.dev.yml
Normal file
@ -0,0 +1,20 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
container_name: envipath-postgres
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: envipath
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@ -2,4 +2,4 @@
|
||||
# Django starts so that shared_task will use this app.
|
||||
from .celery import app as celery_app
|
||||
|
||||
__all__ = ('celery_app',)
|
||||
__all__ = ("celery_app",)
|
||||
|
||||
@ -4,8 +4,6 @@ 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")
|
||||
|
||||
|
||||
@ -11,6 +11,6 @@ import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "envipath.settings")
|
||||
|
||||
application = get_asgi_application()
|
||||
|
||||
@ -4,15 +4,15 @@ 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')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "envipath.settings")
|
||||
|
||||
app = Celery('envipath')
|
||||
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')
|
||||
app.config_from_object("django.conf:settings", namespace="CELERY")
|
||||
|
||||
|
||||
@setup_logging.connect
|
||||
|
||||
@ -9,6 +9,7 @@ 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
|
||||
|
||||
@ -20,34 +21,35 @@ 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)
|
||||
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&@]'
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY", "secret-key")
|
||||
|
||||
# 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(',')
|
||||
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',
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
# 3rd party
|
||||
'django_extensions',
|
||||
'oauth2_provider',
|
||||
"django_extensions",
|
||||
"oauth2_provider",
|
||||
# Custom
|
||||
'epdb',
|
||||
'migration',
|
||||
'epauth',
|
||||
"epdb",
|
||||
"migration",
|
||||
]
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
@ -55,42 +57,42 @@ AUTHENTICATION_BACKENDS = [
|
||||
]
|
||||
|
||||
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',
|
||||
"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')
|
||||
if os.environ.get("REGISTRATION_MANDATORY", False) == "True":
|
||||
MIDDLEWARE.append("epdb.middleware.login_required_middleware.LoginRequiredMiddleware")
|
||||
|
||||
ROOT_URLCONF = 'envipath.urls'
|
||||
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',
|
||||
"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'
|
||||
WSGI_APPLICATION = "envipath.wsgi.application"
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||
@ -98,11 +100,11 @@ WSGI_APPLICATION = 'envipath.wsgi.application'
|
||||
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']
|
||||
"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"],
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,96 +112,87 @@ DATABASES = {
|
||||
# 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',
|
||||
},
|
||||
{"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'
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
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'
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
EMAIL_SUBJECT_PREFIX = "[enviPath] "
|
||||
if DEBUG:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
else:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
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_HOST = "mail.gandi.net"
|
||||
EMAIL_HOST_USER = os.environ["EMAIL_HOST_USER"]
|
||||
EMAIL_HOST_PASSWORD = os.environ["EMAIL_HOST_PASSWORD"]
|
||||
EMAIL_PORT = 587
|
||||
DEFAULT_FROM_EMAIL = os.environ["DEFAULT_FROM_EMAIL"]
|
||||
SERVER_EMAIL = os.environ["SERVER_EMAIL"]
|
||||
|
||||
AUTH_USER_MODEL = "epdb.User"
|
||||
ADMIN_APPROVAL_REQUIRED = os.environ.get('ADMIN_APPROVAL_REQUIRED', 'False') == 'True'
|
||||
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/'
|
||||
LOGIN_URL = "/login/"
|
||||
|
||||
SERVER_URL = os.environ.get('SERVER_URL', 'http://localhost:8000')
|
||||
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'
|
||||
}
|
||||
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')
|
||||
EP_DATA_DIR = os.environ["EP_DATA_DIR"]
|
||||
if not os.path.exists(EP_DATA_DIR):
|
||||
os.mkdir(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')
|
||||
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')
|
||||
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')
|
||||
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/'
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
# Where the sources are stored...
|
||||
STATICFILES_DIRS = (
|
||||
BASE_DIR / 'static',
|
||||
)
|
||||
STATICFILES_DIRS = (BASE_DIR / "static",)
|
||||
|
||||
FIXTURE_DIRS = (
|
||||
BASE_DIR / 'fixtures',
|
||||
)
|
||||
FIXTURE_DIRS = (BASE_DIR / "fixtures",)
|
||||
|
||||
# Logging
|
||||
LOGGING = {
|
||||
@ -207,8 +200,8 @@ LOGGING = {
|
||||
"disable_existing_loggers": True,
|
||||
"formatters": {
|
||||
"simple": {
|
||||
'format': '[%(asctime)s] %(levelname)s %(module)s - %(message)s',
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
"format": "[%(asctime)s] %(levelname)s %(module)s - %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
@ -221,7 +214,7 @@ LOGGING = {
|
||||
"level": "DEBUG", # Or higher
|
||||
"class": "logging.FileHandler",
|
||||
"filename": os.path.join(LOG_DIR, "debug.log"),
|
||||
"formatter": "simple"
|
||||
"formatter": "simple",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
@ -229,72 +222,66 @@ LOGGING = {
|
||||
"epdb": {
|
||||
"handlers": ["file"], # "console",
|
||||
"propagate": True,
|
||||
"level": os.environ.get('LOG_LEVEL', 'INFO')
|
||||
"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')
|
||||
"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')
|
||||
"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")
|
||||
|
||||
ENVIFORMER_PRESENT = os.environ.get("ENVIFORMER_PRESENT", "False") == "True"
|
||||
ENVIFORMER_DEVICE = os.environ.get("ENVIFORMER_DEVICE", "cpu")
|
||||
|
||||
# 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'
|
||||
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'
|
||||
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'
|
||||
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(
|
||||
"base_clf": RandomForestClassifier(
|
||||
n_estimators=100,
|
||||
max_features='log2',
|
||||
max_features="log2",
|
||||
random_state=42,
|
||||
criterion='entropy',
|
||||
criterion="entropy",
|
||||
ccp_alpha=0.0,
|
||||
max_depth=3,
|
||||
min_samples_leaf=1
|
||||
min_samples_leaf=1,
|
||||
),
|
||||
'num_chains': 10,
|
||||
"num_chains": 10,
|
||||
}
|
||||
|
||||
DEFAULT_MODEL_PARAMS = {
|
||||
'base_clf': DecisionTreeClassifier(
|
||||
criterion='entropy',
|
||||
"base_clf": DecisionTreeClassifier(
|
||||
criterion="entropy",
|
||||
max_depth=3,
|
||||
min_samples_split=5,
|
||||
# min_samples_leaf=5,
|
||||
max_features='sqrt',
|
||||
max_features="sqrt",
|
||||
# class_weight='balanced',
|
||||
random_state=42
|
||||
random_state=42,
|
||||
),
|
||||
'num_chains': 10,
|
||||
"num_chains": 10,
|
||||
}
|
||||
|
||||
DEFAULT_MAX_NUMBER_OF_NODES = 30
|
||||
@ -302,9 +289,10 @@ DEFAULT_MAX_DEPTH = 5
|
||||
DEFAULT_MODEL_THRESHOLD = 0.25
|
||||
|
||||
# Loading Plugins
|
||||
PLUGINS_ENABLED = os.environ.get('PLUGINS_ENABLED', 'False') == 'True'
|
||||
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)
|
||||
@ -313,56 +301,59 @@ else:
|
||||
PROPERTY_PLUGINS = {}
|
||||
DESCRIPTOR_PLUGINS = {}
|
||||
|
||||
SENTRY_ENABLED = os.environ.get('SENTRY_ENABLED', 'False') == 'True'
|
||||
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):
|
||||
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'),
|
||||
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'),
|
||||
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,
|
||||
"MODEL_BUILDING": MODEL_BUILDING_ENABLED,
|
||||
"CELERY": FLAG_CELERY_PRESENT,
|
||||
"PLUGINS": PLUGINS_ENABLED,
|
||||
"SENTRY": SENTRY_ENABLED,
|
||||
"ENVIFORMER": ENVIFORMER_PRESENT,
|
||||
"APPLICABILITY_DOMAIN": APPLICABILITY_DOMAIN_ENABLED,
|
||||
}
|
||||
|
||||
# path of the URL are checked via "startswith"
|
||||
# -> /password_reset/done is covered as well
|
||||
LOGIN_EXEMPT_URLS = [
|
||||
'/register',
|
||||
'/api/legacy/',
|
||||
'/o/token/',
|
||||
'/o/userinfo/',
|
||||
'/password_reset/',
|
||||
'/reset/',
|
||||
'/microsoft/',
|
||||
"/register",
|
||||
"/api/legacy/",
|
||||
"/o/token/",
|
||||
"/o/userinfo/",
|
||||
"/password_reset/",
|
||||
"/reset/",
|
||||
"/microsoft/",
|
||||
]
|
||||
|
||||
# MS AD/Entra
|
||||
MS_ENTRA_ENABLED = os.environ.get('MS_ENTRA_ENABLED', 'False') == 'True'
|
||||
MS_ENTRA_ENABLED = os.environ.get("MS_ENTRA_ENABLED", "False") == "True"
|
||||
if MS_ENTRA_ENABLED:
|
||||
MS_ENTRA_CLIENT_ID = os.environ['MS_CLIENT_ID']
|
||||
MS_ENTRA_CLIENT_SECRET = os.environ['MS_CLIENT_SECRET']
|
||||
MS_ENTRA_TENANT_ID = os.environ['MS_TENANT_ID']
|
||||
# Add app to installed apps
|
||||
INSTALLED_APPS.append("epauth")
|
||||
# Set vars required by app
|
||||
MS_ENTRA_CLIENT_ID = os.environ["MS_CLIENT_ID"]
|
||||
MS_ENTRA_CLIENT_SECRET = os.environ["MS_CLIENT_SECRET"]
|
||||
MS_ENTRA_TENANT_ID = os.environ["MS_TENANT_ID"]
|
||||
MS_ENTRA_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}"
|
||||
MS_ENTRA_REDIRECT_URI = os.environ['MS_REDIRECT_URI']
|
||||
MS_ENTRA_SCOPES = os.environ.get('MS_SCOPES', '').split(',')
|
||||
MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"]
|
||||
MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",")
|
||||
|
||||
@ -14,13 +14,14 @@ 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.conf import settings as s
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
from .api import api_v1, api_legacy
|
||||
|
||||
urlpatterns = [
|
||||
path("", include("epauth.urls")),
|
||||
path("", include("epdb.urls")),
|
||||
path("", include("migration.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
@ -28,3 +29,6 @@ urlpatterns = [
|
||||
path("api/legacy/", api_legacy.urls),
|
||||
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
||||
]
|
||||
|
||||
if s.MS_ENTRA_ENABLED:
|
||||
urlpatterns.append(path("", include("epauth.urls")))
|
||||
|
||||
@ -11,6 +11,6 @@ import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "envipath.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
||||
@ -7,6 +7,7 @@ from .models import (
|
||||
GroupPackagePermission,
|
||||
Package,
|
||||
MLRelativeReasoning,
|
||||
EnviFormer,
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
SimpleAmbitRule,
|
||||
@ -16,12 +17,15 @@ from .models import (
|
||||
Node,
|
||||
Edge,
|
||||
Scenario,
|
||||
Setting
|
||||
Setting,
|
||||
ExternalDatabase,
|
||||
ExternalIdentifier,
|
||||
JobLog,
|
||||
)
|
||||
|
||||
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
list_display = ["username", "email", "is_active"]
|
||||
|
||||
|
||||
class UserPackagePermissionAdmin(admin.ModelAdmin):
|
||||
@ -36,17 +40,28 @@ class GroupPackagePermissionAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class JobLogAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class EPAdmin(admin.ModelAdmin):
|
||||
search_fields = ['name', 'description']
|
||||
search_fields = ["name", "description"]
|
||||
list_display = ["name", "url", "created"]
|
||||
ordering = ["-created"]
|
||||
|
||||
|
||||
class PackageAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class MLRelativeReasoningAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class EnviFormerAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class CompoundAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
@ -87,12 +102,22 @@ class SettingAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class ExternalDatabaseAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class ExternalIdentifierAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
|
||||
admin.site.register(Group, GroupAdmin)
|
||||
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
|
||||
admin.site.register(JobLog, JobLogAdmin)
|
||||
admin.site.register(Package, PackageAdmin)
|
||||
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
||||
admin.site.register(EnviFormer, EnviFormerAdmin)
|
||||
admin.site.register(Compound, CompoundAdmin)
|
||||
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
||||
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
||||
@ -103,3 +128,5 @@ admin.site.register(Node, NodeAdmin)
|
||||
admin.site.register(Edge, EdgeAdmin)
|
||||
admin.site.register(Setting, SettingAdmin)
|
||||
admin.site.register(Scenario, ScenarioAdmin)
|
||||
admin.site.register(ExternalDatabase, ExternalDatabaseAdmin)
|
||||
admin.site.register(ExternalIdentifier, ExternalIdentifierAdmin)
|
||||
|
||||
13
epdb/api.py
13
epdb/api.py
@ -21,7 +21,7 @@ class BearerTokenAuth(HttpBearer):
|
||||
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')
|
||||
return get_user_model().objects.get(username="anonymous")
|
||||
|
||||
|
||||
router = Router(auth=BearerTokenAuth())
|
||||
@ -85,7 +85,9 @@ 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!'}
|
||||
return 403, {
|
||||
"message": f"Getting Package with id {package_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/compound", response={200: List[CompoundSchema], 403: Error})
|
||||
@ -97,7 +99,9 @@ def get_compounds(request):
|
||||
return qs
|
||||
|
||||
|
||||
@router.get("/package/{uuid:package_uuid}/compound", response={200: List[CompoundSchema], 403: Error})
|
||||
@router.get(
|
||||
"/package/{uuid:package_uuid}/compound", response={200: List[CompoundSchema], 403: Error}
|
||||
)
|
||||
@paginate
|
||||
def get_package_compounds(request, package_uuid):
|
||||
try:
|
||||
@ -105,4 +109,5 @@ def get_package_compounds(request, 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!'}
|
||||
"message": f"Getting Compounds for Package with id {package_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@ from django.apps import AppConfig
|
||||
|
||||
|
||||
class EPDBConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'epdb'
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "epdb"
|
||||
|
||||
def ready(self):
|
||||
import epdb.signals # noqa: F401
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
from django import forms
|
||||
|
||||
|
||||
class EmailLoginForm(forms.Form):
|
||||
email = forms.EmailField()
|
||||
File diff suppressed because it is too large
Load Diff
1045
epdb/logic.py
1045
epdb/logic.py
File diff suppressed because it is too large
Load Diff
@ -5,32 +5,49 @@ 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
|
||||
from epdb.models import (
|
||||
UserSettingPermission,
|
||||
MLRelativeReasoning,
|
||||
EnviFormer,
|
||||
Permission,
|
||||
User,
|
||||
ExternalDatabase,
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def create_users(self):
|
||||
|
||||
# Anonymous User
|
||||
if not User.objects.filter(email='anon@envipath.com').exists():
|
||||
anon = UserManager.create_user("anonymous", "anon@envipath.com", "SuperSafe",
|
||||
is_active=True, add_to_group=False, set_setting=False)
|
||||
if not User.objects.filter(email="anon@envipath.com").exists():
|
||||
anon = UserManager.create_user(
|
||||
"anonymous",
|
||||
"anon@envipath.com",
|
||||
"SuperSafe",
|
||||
is_active=True,
|
||||
add_to_group=False,
|
||||
set_setting=False,
|
||||
)
|
||||
else:
|
||||
anon = User.objects.get(email='anon@envipath.com')
|
||||
anon = User.objects.get(email="anon@envipath.com")
|
||||
|
||||
# Admin User
|
||||
if not User.objects.filter(email='admin@envipath.com').exists():
|
||||
admin = UserManager.create_user("admin", "admin@envipath.com", "SuperSafe",
|
||||
is_active=True, add_to_group=False, set_setting=False)
|
||||
if not User.objects.filter(email="admin@envipath.com").exists():
|
||||
admin = UserManager.create_user(
|
||||
"admin",
|
||||
"admin@envipath.com",
|
||||
"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@envipath.com')
|
||||
admin = User.objects.get(email="admin@envipath.com")
|
||||
|
||||
# System Group
|
||||
g = GroupManager.create_group(admin, 'enviPath Users', 'All enviPath Users')
|
||||
g = GroupManager.create_group(admin, "enviPath Users", "All enviPath Users")
|
||||
g.public = True
|
||||
g.save()
|
||||
|
||||
@ -43,14 +60,20 @@ class Command(BaseCommand):
|
||||
admin.default_group = g
|
||||
admin.save()
|
||||
|
||||
if not User.objects.filter(email='user0@envipath.com').exists():
|
||||
user0 = UserManager.create_user("user0", "user0@envipath.com", "SuperSafe",
|
||||
is_active=True, add_to_group=False, set_setting=False)
|
||||
if not User.objects.filter(email="user0@envipath.com").exists():
|
||||
user0 = UserManager.create_user(
|
||||
"user0",
|
||||
"user0@envipath.com",
|
||||
"SuperSafe",
|
||||
is_active=True,
|
||||
add_to_group=False,
|
||||
set_setting=False,
|
||||
)
|
||||
user0.is_staff = True
|
||||
user0.is_superuser = True
|
||||
user0.save()
|
||||
else:
|
||||
user0 = User.objects.get(email='user0@envipath.com')
|
||||
user0 = User.objects.get(email="user0@envipath.com")
|
||||
|
||||
g.user_member.add(user0)
|
||||
g.save()
|
||||
@ -61,18 +84,20 @@ class Command(BaseCommand):
|
||||
return anon, admin, g, user0
|
||||
|
||||
def import_package(self, data, owner):
|
||||
return PackageManager.import_legacy_package(data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True)
|
||||
return PackageManager.import_legacy_package(
|
||||
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
||||
)
|
||||
|
||||
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',
|
||||
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
|
||||
model_threshold=None,
|
||||
)
|
||||
|
||||
return s
|
||||
@ -84,54 +109,51 @@ class Command(BaseCommand):
|
||||
"""
|
||||
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 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": "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": "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": "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': '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": "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/{id}",
|
||||
},
|
||||
{
|
||||
'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}"'
|
||||
}
|
||||
"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
|
||||
)
|
||||
ExternalDatabase.objects.get_or_create(name=db_info["name"], defaults=db_info)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
@ -142,20 +164,24 @@ class Command(BaseCommand):
|
||||
|
||||
# Import Packages
|
||||
packages = [
|
||||
'EAWAG-BBD.json',
|
||||
'EAWAG-SOIL.json',
|
||||
'EAWAG-SLUDGE.json',
|
||||
'EAWAG-SEDIMENT.json',
|
||||
"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())
|
||||
package_data = json.loads(
|
||||
open(
|
||||
s.BASE_DIR / "fixtures" / "packages" / "2025-07-18" / p, encoding="utf-8"
|
||||
).read()
|
||||
)
|
||||
imported_package = self.import_package(package_data, admin)
|
||||
mapping[p.replace('.json', '')] = imported_package
|
||||
mapping[p.replace(".json", "")] = imported_package
|
||||
|
||||
setting = self.create_default_setting(admin, [mapping['EAWAG-BBD']])
|
||||
setting = self.create_default_setting(admin, [mapping["EAWAG-BBD"]])
|
||||
setting.public = True
|
||||
setting.save()
|
||||
setting.make_global_default()
|
||||
@ -171,26 +197,28 @@ class Command(BaseCommand):
|
||||
usp.save()
|
||||
|
||||
# Create Model Package
|
||||
pack = PackageManager.create_package(admin, "Public Prediction Models",
|
||||
"Package to make Prediction Models publicly available")
|
||||
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']],
|
||||
rule_packages=[mapping["EAWAG-BBD"]],
|
||||
data_packages=[mapping["EAWAG-BBD"]],
|
||||
eval_packages=[],
|
||||
threshold=0.5,
|
||||
name='ECC - BBD - T0.5',
|
||||
description='ML Relative Reasoning',
|
||||
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)
|
||||
EnviFormer.create(pack, "EnviFormer - T0.5", "EnviFormer Model with Threshold 0.5", 0.5)
|
||||
|
||||
119
epdb/management/commands/create_ml_models.py
Normal file
119
epdb/management/commands/create_ml_models.py
Normal file
@ -0,0 +1,119 @@
|
||||
from django.conf import settings as s
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from epdb.models import MLRelativeReasoning, EnviFormer, Package
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""This command can be run with
|
||||
`python manage.py create_ml_models [model_names] -d [data_packages] FOR MLRR ONLY: -r [rule_packages]
|
||||
OPTIONAL: -e [eval_packages] -t threshold`
|
||||
For example, to train both EnviFormer and MLRelativeReasoning on BBD and SOIL and evaluate them on SLUDGE with a
|
||||
threshold of 0.6, the below command would be used:
|
||||
`python manage.py create_ml_models enviformer mlrr -d bbd soil -e sludge -t 0.6
|
||||
"""
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"model_names",
|
||||
nargs="+",
|
||||
type=str,
|
||||
help="The names of models to train. Options are: enviformer, mlrr",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d", "--data-packages", nargs="+", type=str, help="Packages for training"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e", "--eval-packages", nargs="*", type=str, help="Packages for evaluation", default=[]
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--rule-packages",
|
||||
nargs="*",
|
||||
type=str,
|
||||
help="Rule Packages mandatory for MLRR",
|
||||
default=[],
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--threshold",
|
||||
type=float,
|
||||
help="Model prediction threshold",
|
||||
default=0.5,
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
# Find Public Prediction Models package to add new models to
|
||||
try:
|
||||
pack = Package.objects.filter(name="Public Prediction Models")[0]
|
||||
bbd = Package.objects.filter(name="EAWAG-BBD")[0]
|
||||
soil = Package.objects.filter(name="EAWAG-SOIL")[0]
|
||||
sludge = Package.objects.filter(name="EAWAG-SLUDGE")[0]
|
||||
sediment = Package.objects.filter(name="EAWAG-SEDIMENT")[0]
|
||||
except IndexError:
|
||||
raise IndexError(
|
||||
"Can't find correct packages. They should be created with the bootstrap command"
|
||||
)
|
||||
|
||||
def decode_packages(package_list):
|
||||
"""Decode package strings into their respective packages"""
|
||||
packages = []
|
||||
for p in package_list:
|
||||
p = p.lower()
|
||||
if p == "bbd":
|
||||
packages.append(bbd)
|
||||
elif p == "soil":
|
||||
packages.append(soil)
|
||||
elif p == "sludge":
|
||||
packages.append(sludge)
|
||||
elif p == "sediment":
|
||||
packages.append(sediment)
|
||||
else:
|
||||
raise ValueError(f"Unknown package {p}")
|
||||
return packages
|
||||
|
||||
# Iteratively create models in options["model_names"]
|
||||
print(f"Creating models: {options['model_names']}\n"
|
||||
f"Data packages: {options['data_packages']}\n"
|
||||
f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
|
||||
f"Eval Packages: {options['eval_packages']}\n"
|
||||
f"Threshold: {options['threshold']:.2f}")
|
||||
data_packages = decode_packages(options["data_packages"])
|
||||
eval_packages = decode_packages(options["eval_packages"])
|
||||
rule_packages = decode_packages(options["rule_packages"])
|
||||
for model_name in options["model_names"]:
|
||||
model_name = model_name.lower()
|
||||
if model_name == "enviformer" and s.ENVIFORMER_PRESENT:
|
||||
model = EnviFormer.create(
|
||||
pack,
|
||||
data_packages=data_packages,
|
||||
eval_packages=eval_packages,
|
||||
threshold=options['threshold'],
|
||||
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||
description=f"EnviFormer transformer trained on {options['data_packages']} "
|
||||
f"evaluated on {options['eval_packages']}.",
|
||||
)
|
||||
elif model_name == "mlrr":
|
||||
model = MLRelativeReasoning.create(
|
||||
package=pack,
|
||||
rule_packages=rule_packages,
|
||||
data_packages=data_packages,
|
||||
eval_packages=eval_packages,
|
||||
threshold=options['threshold'],
|
||||
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
|
||||
f"{options['rule_packages']} and evaluated on {options['eval_packages']}.",
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Cannot create model of type {model_name}, unknown model type")
|
||||
# Build the dataset for the model, train it, evaluate it and save it
|
||||
print(f"Building dataset for {model_name}")
|
||||
model.build_dataset()
|
||||
print(f"Training {model_name}")
|
||||
model.build_model()
|
||||
print(f"Evaluating {model_name}")
|
||||
model.evaluate_model(False, eval_packages=eval_packages)
|
||||
print(f"Saving {model_name}")
|
||||
model.save()
|
||||
59
epdb/management/commands/dump_enviformer.py
Normal file
59
epdb/management/commands/dump_enviformer.py
Normal file
@ -0,0 +1,59 @@
|
||||
import json
|
||||
import os
|
||||
import tarfile
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from django.conf import settings as s
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from epdb.models import EnviFormer
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"model",
|
||||
type=str,
|
||||
help="Model UUID of the Model to Dump",
|
||||
)
|
||||
parser.add_argument("--output", type=str)
|
||||
|
||||
def package_dict_and_folder(self, dict_data, folder_path, output_path):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
dict_filename = os.path.join(tmpdir, "data.json")
|
||||
|
||||
with open(dict_filename, "w", encoding="utf-8") as f:
|
||||
json.dump(dict_data, f, indent=2)
|
||||
|
||||
with tarfile.open(output_path, "w:gz") as tar:
|
||||
tar.add(dict_filename, arcname="data.json")
|
||||
tar.add(folder_path, arcname=os.path.basename(folder_path))
|
||||
|
||||
os.remove(dict_filename)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
output = options["output"]
|
||||
|
||||
if os.path.exists(output):
|
||||
raise ValueError(f"Output file {output} already exists")
|
||||
|
||||
model = EnviFormer.objects.get(uuid=options["model"])
|
||||
|
||||
data = {
|
||||
"uuid": str(model.uuid),
|
||||
"name": model.name,
|
||||
"description": model.description,
|
||||
"kv": model.kv,
|
||||
"data_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
|
||||
"eval_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
|
||||
"threshold": model.threshold,
|
||||
"eval_results": model.eval_results,
|
||||
"multigen_eval": model.multigen_eval,
|
||||
"model_status": model.model_status,
|
||||
}
|
||||
|
||||
model_folder = os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid))
|
||||
|
||||
self.package_dict_and_folder(data, model_folder, output)
|
||||
58
epdb/management/commands/import_external_identifiers.py
Normal file
58
epdb/management/commands/import_external_identifiers.py
Normal file
@ -0,0 +1,58 @@
|
||||
from csv import DictReader
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from epdb.models import Compound, CompoundStructure, Reaction, ExternalDatabase, ExternalIdentifier
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
STR_TO_MODEL = {
|
||||
"Compound": Compound,
|
||||
"CompoundStructure": CompoundStructure,
|
||||
"Reaction": Reaction,
|
||||
}
|
||||
|
||||
STR_TO_DATABASE = {
|
||||
"ChEBI": ExternalDatabase.objects.get(name="ChEBI"),
|
||||
"RHEA": ExternalDatabase.objects.get(name="RHEA"),
|
||||
"KEGG Reaction": ExternalDatabase.objects.get(name="KEGG Reaction"),
|
||||
"PubChem Compound": ExternalDatabase.objects.get(name="PubChem Compound"),
|
||||
"PubChem Substance": ExternalDatabase.objects.get(name="PubChem Substance"),
|
||||
}
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--data",
|
||||
type=str,
|
||||
help="Path of the ID Mapping file.",
|
||||
required=True,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--replace-host",
|
||||
type=str,
|
||||
help="Replace https://envipath.org/ with this host, e.g. http://localhost:8000/",
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
with open(options["data"]) as fh:
|
||||
reader = DictReader(fh)
|
||||
for row in reader:
|
||||
clz = self.STR_TO_MODEL[row["model"]]
|
||||
|
||||
url = row["url"]
|
||||
if options["replace_host"]:
|
||||
url = url.replace("https://envipath.org/", options["replace_host"])
|
||||
|
||||
instance = clz.objects.get(url=url)
|
||||
db = self.STR_TO_DATABASE[row["identifier_type"]]
|
||||
|
||||
ExternalIdentifier.objects.create(
|
||||
content_object=instance,
|
||||
database=db,
|
||||
identifier_value=row["identifier_value"],
|
||||
url=db.url_pattern.format(id=row["identifier_value"]),
|
||||
is_primary=False,
|
||||
)
|
||||
@ -1,27 +1,29 @@
|
||||
import json
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from epdb.logic import PackageManager
|
||||
from epdb.models import *
|
||||
from epdb.models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--data',
|
||||
"--data",
|
||||
type=str,
|
||||
help='Path of the Package to import.',
|
||||
help="Path of the Package to import.",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--owner',
|
||||
"--owner",
|
||||
type=str,
|
||||
help='Username of the desired Owner.',
|
||||
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']))
|
||||
owner = User.objects.get(username=options["owner"])
|
||||
package_data = json.load(open(options["data"]))
|
||||
PackageManager.import_legacy_package(package_data, owner)
|
||||
|
||||
81
epdb/management/commands/load_enviformer.py
Normal file
81
epdb/management/commands/load_enviformer.py
Normal file
@ -0,0 +1,81 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tarfile
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from django.conf import settings as s
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from epdb.models import EnviFormer, Package
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"input",
|
||||
type=str,
|
||||
help=".tar.gz file containing the Model dump.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"package",
|
||||
type=str,
|
||||
help="Package UUID where the Model should be loaded to.",
|
||||
)
|
||||
|
||||
def read_dict_and_folder_from_archive(self, archive_path, extract_to="extracted_folder"):
|
||||
with tarfile.open(archive_path, "r:gz") as tar:
|
||||
tar.extractall(extract_to)
|
||||
|
||||
dict_path = os.path.join(extract_to, "data.json")
|
||||
|
||||
if not os.path.exists(dict_path):
|
||||
raise FileNotFoundError("data.json not found in the archive.")
|
||||
|
||||
with open(dict_path, "r", encoding="utf-8") as f:
|
||||
data_dict = json.load(f)
|
||||
|
||||
extracted_items = os.listdir(extract_to)
|
||||
folders = [item for item in extracted_items if item != "data.json"]
|
||||
folder_path = os.path.join(extract_to, folders[0]) if folders else None
|
||||
|
||||
return data_dict, folder_path
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
if not os.path.exists(options["input"]):
|
||||
raise ValueError(f"Input file {options['input']} does not exist.")
|
||||
|
||||
target_package = Package.objects.get(uuid=options["package"])
|
||||
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
data, folder = self.read_dict_and_folder_from_archive(options["input"], tmpdir)
|
||||
|
||||
model = EnviFormer()
|
||||
model.package = target_package
|
||||
# model.uuid = data["uuid"]
|
||||
model.name = data["name"]
|
||||
model.description = data["description"]
|
||||
model.kv = data["kv"]
|
||||
model.threshold = float(data["threshold"])
|
||||
model.eval_results = data["eval_results"]
|
||||
model.multigen_eval = data["multigen_eval"]
|
||||
model.model_status = data["model_status"]
|
||||
model.save()
|
||||
|
||||
for p_uuid in data["data_packages_uuids"]:
|
||||
p = Package.objects.get(uuid=p_uuid)
|
||||
model.data_packages.add(p)
|
||||
|
||||
for p_uuid in data["eval_packages_uuids"]:
|
||||
p = Package.objects.get(uuid=p_uuid)
|
||||
model.eval_packages.add(p)
|
||||
|
||||
target_folder = os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid))
|
||||
|
||||
shutil.copytree(folder, target_folder)
|
||||
os.rename(
|
||||
os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid), f"{data['uuid']}.ckpt"),
|
||||
os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid), f"{model.uuid}.ckpt"),
|
||||
)
|
||||
@ -1,51 +1,64 @@
|
||||
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
|
||||
from django.db.models import F, Value, TextField, JSONField
|
||||
from django.db.models.functions import Replace, Cast
|
||||
|
||||
from epdb.models import EnviPathModel
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--old',
|
||||
"--old",
|
||||
type=str,
|
||||
help='Old Host, most likely https://envipath.org/',
|
||||
help="Old Host, most likely https://envipath.org/",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--new',
|
||||
"--new",
|
||||
type=str,
|
||||
help='New Host, most likely http://localhost:8000/',
|
||||
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',
|
||||
'RuleBasedRelativeReasoning',
|
||||
'EnviFormer',
|
||||
'ApplicabilityDomain',
|
||||
"User",
|
||||
"Group",
|
||||
"Package",
|
||||
"Compound",
|
||||
"CompoundStructure",
|
||||
"Pathway",
|
||||
"Edge",
|
||||
"Node",
|
||||
"Reaction",
|
||||
"SimpleAmbitRule",
|
||||
"SimpleRDKitRule",
|
||||
"ParallelRule",
|
||||
"SequentialRule",
|
||||
"Scenario",
|
||||
"Setting",
|
||||
"MLRelativeReasoning",
|
||||
"RuleBasedRelativeReasoning",
|
||||
"EnviFormer",
|
||||
"ApplicabilityDomain",
|
||||
"EnzymeLink",
|
||||
]
|
||||
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']))
|
||||
url=Replace(F("url"), Value(options["old"]), Value(options["new"]))
|
||||
)
|
||||
if issubclass(obj_cls, EnviPathModel):
|
||||
obj_cls.objects.update(
|
||||
kv=Cast(
|
||||
Replace(
|
||||
Cast(F("kv"), output_field=TextField()),
|
||||
Value(options["old"]),
|
||||
Value(options["new"]),
|
||||
),
|
||||
output_field=JSONField(),
|
||||
)
|
||||
)
|
||||
|
||||
38
epdb/management/commands/update_job_logs.py
Normal file
38
epdb/management/commands/update_job_logs.py
Normal file
@ -0,0 +1,38 @@
|
||||
from datetime import date, timedelta
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from epdb.models import JobLog
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--cleanup",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Remove all logs older than this number of days. Default is None, which does not remove any logs.",
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
if options["cleanup"] is not None:
|
||||
cleanup_dt = date.today() - timedelta(days=options["cleanup"])
|
||||
print(JobLog.objects.filter(created__lt=cleanup_dt).delete())
|
||||
|
||||
logs = JobLog.objects.filter(status="INITIAL")
|
||||
print(f"Found {logs.count()} logs to update")
|
||||
updated = 0
|
||||
for log in logs:
|
||||
res = log.check_for_update()
|
||||
if res:
|
||||
updated += 1
|
||||
|
||||
print(f"Updated {updated} logs")
|
||||
|
||||
from django.db.models import Count
|
||||
|
||||
qs = JobLog.objects.values("status").annotate(total=Count("status"))
|
||||
for r in qs:
|
||||
print(r["status"], r["total"])
|
||||
@ -3,22 +3,25 @@ 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', [])
|
||||
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())}")
|
||||
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)
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
# Generated by Django 5.2.1 on 2025-10-07 08:19
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('epdb', '0006_mlrelativereasoning_multigen_eval_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='enviformer',
|
||||
options={},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='enviformer',
|
||||
name='app_domain',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='enviformer',
|
||||
name='data_packages',
|
||||
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='enviformer',
|
||||
name='eval_packages',
|
||||
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='enviformer',
|
||||
name='eval_results',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='enviformer',
|
||||
name='model_status',
|
||||
field=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'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='enviformer',
|
||||
name='multigen_eval',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='enviformer',
|
||||
name='rule_packages',
|
||||
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages'),
|
||||
),
|
||||
]
|
||||
64
epdb/migrations/0008_enzymelink.py
Normal file
64
epdb/migrations/0008_enzymelink.py
Normal file
@ -0,0 +1,64 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-10 06:58
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("epdb", "0007_alter_enviformer_options_enviformer_app_domain_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="EnzymeLink",
|
||||
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"),
|
||||
),
|
||||
("url", models.TextField(null=True, unique=True, verbose_name="URL")),
|
||||
("kv", models.JSONField(blank=True, default=dict, null=True)),
|
||||
("ec_number", models.TextField(verbose_name="EC Number")),
|
||||
("classification_level", models.IntegerField(verbose_name="Classification Level")),
|
||||
("linking_method", models.TextField(verbose_name="Linking Method")),
|
||||
("edge_evidence", models.ManyToManyField(to="epdb.edge")),
|
||||
("reaction_evidence", models.ManyToManyField(to="epdb.reaction")),
|
||||
(
|
||||
"rule",
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="epdb.rule"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
]
|
||||
66
epdb/migrations/0009_joblog.py
Normal file
66
epdb/migrations/0009_joblog.py
Normal file
@ -0,0 +1,66 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-27 09:39
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("epdb", "0008_enzymelink"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="JobLog",
|
||||
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"
|
||||
),
|
||||
),
|
||||
("task_id", models.UUIDField(unique=True)),
|
||||
("job_name", models.TextField()),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("INITIAL", "Initial"),
|
||||
("SUCCESS", "Success"),
|
||||
("FAILURE", "Failure"),
|
||||
("REVOKED", "Revoked"),
|
||||
("IGNORED", "Ignored"),
|
||||
],
|
||||
default="INITIAL",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
("done_at", models.DateTimeField(blank=True, default=None, null=True)),
|
||||
("task_result", models.TextField(blank=True, default=None, null=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
]
|
||||
2133
epdb/models.py
2133
epdb/models.py
File diff suppressed because it is too large
Load Diff
239
epdb/tasks.py
239
epdb/tasks.py
@ -1,58 +1,154 @@
|
||||
import csv
|
||||
import io
|
||||
import logging
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, List, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from celery import shared_task
|
||||
from epdb.models import Pathway, Node, Edge, EPModel, Setting
|
||||
from epdb.logic import SPathway
|
||||
from celery.utils.functional import LRUCache
|
||||
|
||||
from epdb.logic import SPathway
|
||||
from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
||||
|
||||
|
||||
@shared_task(queue='background')
|
||||
def get_ml_model(model_pk: int):
|
||||
if model_pk not in ML_CACHE:
|
||||
ML_CACHE[model_pk] = EPModel.objects.get(id=model_pk)
|
||||
return ML_CACHE[model_pk]
|
||||
|
||||
|
||||
def dispatch_eager(user: "User", job: Callable, *args, **kwargs):
|
||||
try:
|
||||
x = job(*args, **kwargs)
|
||||
log = JobLog()
|
||||
log.user = user
|
||||
log.task_id = uuid4()
|
||||
log.job_name = job.__name__
|
||||
log.status = "SUCCESS"
|
||||
log.done_at = datetime.now()
|
||||
log.task_result = str(x) if x else None
|
||||
log.save()
|
||||
|
||||
return x
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
raise e
|
||||
|
||||
|
||||
def dispatch(user: "User", job: Callable, *args, **kwargs):
|
||||
try:
|
||||
x = job.delay(*args, **kwargs)
|
||||
log = JobLog()
|
||||
log.user = user
|
||||
log.task_id = x.task_id
|
||||
log.job_name = job.__name__
|
||||
log.status = "INITIAL"
|
||||
log.save()
|
||||
|
||||
return x.result
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
raise e
|
||||
|
||||
|
||||
@shared_task(queue="background")
|
||||
def mul(a, b):
|
||||
return a * b
|
||||
|
||||
|
||||
@shared_task(queue='predict')
|
||||
@shared_task(queue="predict")
|
||||
def predict_simple(model_pk: int, smiles: str):
|
||||
mod = EPModel.objects.get(id=model_pk)
|
||||
mod = get_ml_model(model_pk)
|
||||
res = mod.predict(smiles)
|
||||
return res
|
||||
|
||||
|
||||
@shared_task(queue='background')
|
||||
@shared_task(queue="background")
|
||||
def send_registration_mail(user_pk: int):
|
||||
pass
|
||||
|
||||
|
||||
@shared_task(queue='model')
|
||||
def build_model(model_pk: int):
|
||||
@shared_task(bind=True, queue="model")
|
||||
def build_model(self, model_pk: int):
|
||||
mod = EPModel.objects.get(id=model_pk)
|
||||
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=mod.url)
|
||||
|
||||
try:
|
||||
mod.build_dataset()
|
||||
mod.build_model()
|
||||
except Exception as e:
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
JobLog.objects.filter(task_id=self.request.id).update(
|
||||
status="FAILED", task_result=mod.url
|
||||
)
|
||||
|
||||
raise e
|
||||
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=mod.url)
|
||||
|
||||
return mod.url
|
||||
|
||||
|
||||
@shared_task(queue='model')
|
||||
def evaluate_model(model_pk: int):
|
||||
@shared_task(bind=True, queue="model")
|
||||
def evaluate_model(self, model_pk: int, multigen: bool, package_pks: Optional[list] = None):
|
||||
packages = None
|
||||
|
||||
if package_pks:
|
||||
packages = Package.objects.filter(pk__in=package_pks)
|
||||
|
||||
mod = EPModel.objects.get(id=model_pk)
|
||||
mod.evaluate_model()
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=mod.url)
|
||||
|
||||
try:
|
||||
mod.evaluate_model(multigen, eval_packages=packages)
|
||||
except Exception as e:
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
JobLog.objects.filter(task_id=self.request.id).update(
|
||||
status="FAILED", task_result=mod.url
|
||||
)
|
||||
|
||||
raise e
|
||||
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=mod.url)
|
||||
|
||||
return mod.url
|
||||
|
||||
|
||||
@shared_task(queue='model')
|
||||
@shared_task(queue="model")
|
||||
def retrain(model_pk: int):
|
||||
mod = EPModel.objects.get(id=model_pk)
|
||||
mod.retrain()
|
||||
|
||||
|
||||
@shared_task(queue='predict')
|
||||
def predict(pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_pk: Optional[int] = None) -> Pathway:
|
||||
@shared_task(bind=True, queue="predict")
|
||||
def predict(
|
||||
self,
|
||||
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)
|
||||
# If the setting has a model add/restore it from the cache
|
||||
if setting.model is not None:
|
||||
setting.model = get_ml_model(setting.model.pk)
|
||||
|
||||
pw.kv.update(**{'status': 'running'})
|
||||
pw.kv.update(**{"status": "running"})
|
||||
pw.save()
|
||||
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=pw.url)
|
||||
|
||||
try:
|
||||
# regular prediction
|
||||
if limit is not None:
|
||||
@ -74,12 +170,115 @@ def predict(pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_
|
||||
else:
|
||||
raise ValueError("Neither limit nor node_pk given!")
|
||||
|
||||
|
||||
|
||||
except Exception as e:
|
||||
pw.kv.update({'status': 'failed'})
|
||||
pw.kv.update({"status": "failed"})
|
||||
pw.kv.update(**{"error": str(e)})
|
||||
pw.save()
|
||||
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
JobLog.objects.filter(task_id=self.request.id).update(
|
||||
status="FAILED", task_result=pw.url
|
||||
)
|
||||
|
||||
raise e
|
||||
|
||||
pw.kv.update(**{'status': 'completed'})
|
||||
pw.kv.update(**{"status": "completed"})
|
||||
pw.save()
|
||||
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=pw.url)
|
||||
|
||||
return pw.url
|
||||
|
||||
|
||||
@shared_task(bind=True, queue="background")
|
||||
def identify_missing_rules(
|
||||
self,
|
||||
pw_pks: List[int],
|
||||
rule_package_pk: int,
|
||||
):
|
||||
from utilities.misc import PathwayUtils
|
||||
|
||||
rules = Package.objects.get(pk=rule_package_pk).get_applicable_rules()
|
||||
|
||||
rows: List[Any] = []
|
||||
header = [
|
||||
"Package Name",
|
||||
"Pathway Name",
|
||||
"Educt Name",
|
||||
"Educt SMILES",
|
||||
"Reaction Name",
|
||||
"Reaction SMIRKS",
|
||||
"Triggered Rules",
|
||||
"Reactant SMARTS",
|
||||
"Product SMARTS",
|
||||
"Product Names",
|
||||
"Product SMILES",
|
||||
]
|
||||
|
||||
rows.append(header)
|
||||
|
||||
for pw in Pathway.objects.filter(pk__in=pw_pks):
|
||||
pu = PathwayUtils(pw)
|
||||
|
||||
missing_rules = pu.find_missing_rules(rules)
|
||||
|
||||
package_name = pw.package.name
|
||||
pathway_name = pw.name
|
||||
|
||||
for edge_url, rule_chain in missing_rules.items():
|
||||
row: List[Any] = [package_name, pathway_name]
|
||||
edge = Edge.objects.get(url=edge_url)
|
||||
educts = edge.start_nodes.all()
|
||||
|
||||
for educt in educts:
|
||||
row.append(educt.default_node_label.name)
|
||||
row.append(educt.default_node_label.smiles)
|
||||
|
||||
row.append(edge.edge_label.name)
|
||||
row.append(edge.edge_label.smirks())
|
||||
|
||||
rule_names = []
|
||||
reactant_smarts = []
|
||||
product_smarts = []
|
||||
|
||||
for r in rule_chain:
|
||||
r = Rule.objects.get(url=r[0])
|
||||
rule_names.append(r.name)
|
||||
|
||||
rs = r.reactants_smarts
|
||||
if isinstance(rs, set):
|
||||
rs = list(rs)
|
||||
|
||||
ps = r.products_smarts
|
||||
if isinstance(ps, set):
|
||||
ps = list(ps)
|
||||
|
||||
reactant_smarts.append(rs)
|
||||
product_smarts.append(ps)
|
||||
|
||||
row.append(rule_names)
|
||||
row.append(reactant_smarts)
|
||||
row.append(product_smarts)
|
||||
|
||||
products = edge.end_nodes.all()
|
||||
product_names = []
|
||||
product_smiles = []
|
||||
|
||||
for product in products:
|
||||
product_names.append(product.default_node_label.name)
|
||||
product_smiles.append(product.default_node_label.smiles)
|
||||
|
||||
row.append(product_names)
|
||||
row.append(product_smiles)
|
||||
|
||||
rows.append(row)
|
||||
|
||||
buffer = io.StringIO()
|
||||
|
||||
writer = csv.writer(buffer)
|
||||
writer.writerows(rows)
|
||||
|
||||
buffer.seek(0)
|
||||
|
||||
return buffer.getvalue()
|
||||
|
||||
@ -1,7 +1,21 @@
|
||||
from django import template
|
||||
from pydantic import AnyHttpUrl, ValidationError
|
||||
from pydantic.type_adapter import TypeAdapter
|
||||
|
||||
register = template.Library()
|
||||
|
||||
url_adapter = TypeAdapter(AnyHttpUrl)
|
||||
|
||||
|
||||
@register.filter
|
||||
def classname(obj):
|
||||
return obj.__class__.__name__
|
||||
|
||||
|
||||
@register.filter
|
||||
def is_url(value):
|
||||
try:
|
||||
url_adapter.validate_python(value)
|
||||
return True
|
||||
except ValidationError:
|
||||
return False
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
247
epdb/urls.py
247
epdb/urls.py
@ -1,99 +1,196 @@
|
||||
from django.urls import path, re_path
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import path, 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}'
|
||||
UUID = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||
|
||||
urlpatterns = [
|
||||
# Home
|
||||
re_path(r'^$', v.index, name='index'),
|
||||
|
||||
re_path(r"^$", v.index, name="index"),
|
||||
# Login
|
||||
re_path(r'^login', v.login, name='login'),
|
||||
re_path(r'^logout', v.logout, name='logout'),
|
||||
re_path(r'^register', v.register, name='register'),
|
||||
|
||||
# Built In views
|
||||
path('password_reset/', auth_views.PasswordResetView.as_view(
|
||||
template_name='static/password_reset_form.html'
|
||||
), name='password_reset'),
|
||||
|
||||
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(
|
||||
template_name='static/password_reset_done.html'
|
||||
), name='password_reset_done'),
|
||||
|
||||
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(
|
||||
template_name='static/password_reset_confirm.html'
|
||||
), name='password_reset_confirm'),
|
||||
|
||||
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(
|
||||
template_name='static/password_reset_complete.html'
|
||||
), name='password_reset_complete'),
|
||||
|
||||
|
||||
re_path(r"^login", v.login, name="login"),
|
||||
re_path(r"^logout", v.logout, name="logout"),
|
||||
re_path(r"^register", v.register, name="register"),
|
||||
# Built-In views
|
||||
path(
|
||||
"password_reset/",
|
||||
auth_views.PasswordResetView.as_view(template_name="static/password_reset_form.html"),
|
||||
name="password_reset",
|
||||
),
|
||||
path(
|
||||
"password_reset/done/",
|
||||
auth_views.PasswordResetDoneView.as_view(template_name="static/password_reset_done.html"),
|
||||
name="password_reset_done",
|
||||
),
|
||||
path(
|
||||
"reset/<uidb64>/<token>/",
|
||||
auth_views.PasswordResetConfirmView.as_view(
|
||||
template_name="static/password_reset_confirm.html"
|
||||
),
|
||||
name="password_reset_confirm",
|
||||
),
|
||||
path(
|
||||
"reset/done/",
|
||||
auth_views.PasswordResetCompleteView.as_view(
|
||||
template_name="static/password_reset_complete.html"
|
||||
),
|
||||
name="password_reset_complete",
|
||||
),
|
||||
# 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'),
|
||||
|
||||
|
||||
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'),
|
||||
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'),
|
||||
|
||||
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'),
|
||||
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'),
|
||||
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'),
|
||||
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'),
|
||||
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",
|
||||
# ),
|
||||
# EnzymeLinks
|
||||
re_path(
|
||||
rf"^package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
|
||||
v.package_rule_enzymelink,
|
||||
name="package rule enzymelink detail",
|
||||
),
|
||||
re_path(
|
||||
rf"^package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
|
||||
v.package_rule_enzymelink,
|
||||
name="package rule enzymelink detail",
|
||||
),
|
||||
re_path(
|
||||
rf"^package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
|
||||
v.package_rule_enzymelink,
|
||||
name="package rule enzymelink 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'),
|
||||
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'),
|
||||
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'),
|
||||
re_path(
|
||||
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$",
|
||||
v.package_pathway_nodes,
|
||||
name="package pathway node list",
|
||||
),
|
||||
re_path(
|
||||
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node/(?P<node_uuid>{UUID})$",
|
||||
v.package_pathway_node,
|
||||
name="package pathway node detail",
|
||||
),
|
||||
# Pathway Edges
|
||||
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge$', v.package_pathway_edges, name='package pathway edge list'),
|
||||
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge/(?P<edge_uuid>{UUID})$', v.package_pathway_edge, name='package pathway edge detail'),
|
||||
re_path(
|
||||
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge$",
|
||||
v.package_pathway_edges,
|
||||
name="package pathway edge list",
|
||||
),
|
||||
re_path(
|
||||
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge/(?P<edge_uuid>{UUID})$",
|
||||
v.package_pathway_edge,
|
||||
name="package pathway edge detail",
|
||||
),
|
||||
# Scenario
|
||||
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario$', v.package_scenarios, name='package scenario list'),
|
||||
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario/(?P<scenario_uuid>{UUID})$', v.package_scenario, name='package scenario detail'),
|
||||
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'),
|
||||
|
||||
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"),
|
||||
re_path(r"^jobs", v.jobs, name="jobs"),
|
||||
# OAuth Stuff
|
||||
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
|
||||
]
|
||||
|
||||
2386
epdb/views.py
2386
epdb/views.py
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
BIN
fixtures/models/2750eaca-bc13-4018-81c2-f7f9d94bc435_ds.pkl
Normal file
BIN
fixtures/models/2750eaca-bc13-4018-81c2-f7f9d94bc435_ds.pkl
Normal file
Binary file not shown.
BIN
fixtures/models/2750eaca-bc13-4018-81c2-f7f9d94bc435_mod.pkl
Normal file
BIN
fixtures/models/2750eaca-bc13-4018-81c2-f7f9d94bc435_mod.pkl
Normal file
Binary file not shown.
8328
fixtures/packages/2025-07-18/external_identifiers.csv
Normal file
8328
fixtures/packages/2025-07-18/external_identifiers.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
fixtures/test_fixtures_incl_model.jsonl.gz
Normal file
BIN
fixtures/test_fixtures_incl_model.jsonl.gz
Normal file
Binary file not shown.
@ -12,4 +12,5 @@ urlpatterns = [
|
||||
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'),
|
||||
re_path(rf'^migration/compare$', v.compare, name='compare'),
|
||||
]
|
||||
|
||||
@ -1,45 +1,52 @@
|
||||
import gzip
|
||||
import json
|
||||
import logging
|
||||
import os.path
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings as s
|
||||
from django.http import HttpResponseNotAllowed
|
||||
from django.shortcuts import render
|
||||
|
||||
from epdb.logic import PackageManager
|
||||
from epdb.models import Rule
|
||||
from epdb.models import Rule, SimpleAmbitRule, Package, CompoundStructure
|
||||
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)
|
||||
from rdkit import Chem
|
||||
from rdkit.Chem.MolStandardize import rdMolStandardize
|
||||
|
||||
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'))
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
results = []
|
||||
|
||||
success = 0
|
||||
error = 0
|
||||
total = 0
|
||||
def normalize_smiles(smiles):
|
||||
m1 = Chem.MolFromSmiles(smiles)
|
||||
if m1 is None:
|
||||
print("Couldnt read smi: ", smiles)
|
||||
return smiles
|
||||
Chem.RemoveStereochemistry(m1)
|
||||
# Normalizer takes care of charge/tautomer/resonance standardization
|
||||
normalizer = rdMolStandardize.Normalizer()
|
||||
return Chem.MolToSmiles(normalizer.normalize(m1), canonical=True)
|
||||
|
||||
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()
|
||||
def run_both_engines(SMILES, SMIRKS):
|
||||
from envipy_ambit import apply
|
||||
|
||||
res = True
|
||||
ambit_res = apply(SMIRKS, SMILES)
|
||||
# ambit_res, ambit_errors = FormatConverter.sanitize_smiles([str(s) for s in ambit_res])
|
||||
|
||||
for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']):
|
||||
ambit_res = list(
|
||||
set(
|
||||
[
|
||||
normalize_smiles(str(x))
|
||||
for x in FormatConverter.sanitize_smiles([str(s) for s in ambit_res])[0]
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
products = FormatConverter.apply(comp['smiles'], smirks)
|
||||
products = FormatConverter.apply(SMILES, SMIRKS)
|
||||
|
||||
all_rdkit_prods = []
|
||||
for ps in products:
|
||||
@ -47,34 +54,70 @@ def migration(request):
|
||||
all_rdkit_prods.append(p)
|
||||
|
||||
all_rdkit_prods = list(set(all_rdkit_prods))
|
||||
# all_rdkit_res, rdkit_errors = FormatConverter.sanitize_smiles(all_rdkit_prods)
|
||||
all_rdkit_res = list(
|
||||
set(
|
||||
[
|
||||
normalize_smiles(str(x))
|
||||
for x in FormatConverter.sanitize_smiles(
|
||||
[str(s) for s in all_rdkit_prods]
|
||||
)[0]
|
||||
]
|
||||
)
|
||||
)
|
||||
# return ambit_res, ambit_errors, all_rdkit_res, rdkit_errors
|
||||
return ambit_res, 0, all_rdkit_res, 0
|
||||
|
||||
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)
|
||||
def migration(request):
|
||||
if request.method == "GET":
|
||||
context = get_base_context(request)
|
||||
|
||||
# TODO mode "intersection"
|
||||
# partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0)
|
||||
# FAILED (failures=37)
|
||||
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:
|
||||
BBD = Package.objects.get(
|
||||
url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1"
|
||||
)
|
||||
ALL_SMILES = [
|
||||
cs.smiles
|
||||
for cs in CompoundStructure.objects.filter(compound__package=BBD)
|
||||
]
|
||||
RULES = SimpleAmbitRule.objects.filter(package=BBD)
|
||||
|
||||
# TODO mode = "full ambit"
|
||||
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles)
|
||||
# FAILED (failures=46)
|
||||
results = list()
|
||||
num_rules = len(RULES)
|
||||
success = 0
|
||||
error = 0
|
||||
total = 0
|
||||
|
||||
# TODO mode = "equality"
|
||||
partial_res = set(ambit_smiles) == set(rdkit_smiles)
|
||||
# FAILED (failures=69)
|
||||
for i, r in enumerate(RULES):
|
||||
logger.debug(f"\r{i + 1:03d}/{num_rules}")
|
||||
res = True
|
||||
for smiles in ALL_SMILES:
|
||||
try:
|
||||
ambit_res, _, rdkit_res, _ = run_both_engines(smiles, r.smirks)
|
||||
|
||||
res &= partial_res
|
||||
res &= set(ambit_res) == set(rdkit_res)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
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/', '')
|
||||
"name": r.name,
|
||||
"detail_url": s.SERVER_URL
|
||||
+ "/migration/"
|
||||
+ r.url.replace("https://envipath.org/", "").replace(
|
||||
"http://localhost:8000/", ""
|
||||
),
|
||||
"id": str(r.uuid),
|
||||
"url": r.url,
|
||||
"status": res,
|
||||
}
|
||||
)
|
||||
|
||||
@ -84,95 +127,82 @@ def migration(request):
|
||||
error += 1
|
||||
|
||||
total += 1
|
||||
|
||||
results = sorted(results, key=lambda x: (x['status'], x['name']))
|
||||
results = sorted(results, key=lambda x: (x["status"], x["name"]))
|
||||
|
||||
migration_status = {
|
||||
'results': results,
|
||||
'success': success,
|
||||
'error': error,
|
||||
'total': total
|
||||
"results": results,
|
||||
"success": success,
|
||||
"error": error,
|
||||
"total": total,
|
||||
}
|
||||
|
||||
json.dump(migration_status, open(s.BASE_DIR / 'fixtures' / 'migration_status_per_rule.json', 'w'))
|
||||
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)
|
||||
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)
|
||||
return render(request, "migration.html", context)
|
||||
|
||||
|
||||
def migration_detail(request, package_uuid, rule_uuid):
|
||||
current_user = _anonymous_or_real(request)
|
||||
|
||||
if request.method == 'GET':
|
||||
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)
|
||||
BBD = Package.objects.get(name="EAWAG-BBD")
|
||||
STRUCTURES = CompoundStructure.objects.filter(compound__package=BBD)
|
||||
rule = Rule.objects.get(package=BBD, 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 = []
|
||||
smirks = rule.smirks
|
||||
|
||||
res = True
|
||||
results = []
|
||||
|
||||
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 structure in STRUCTURES:
|
||||
ambit_smiles, ambit_errors, rdkit_smiles, rdkit_errors = run_both_engines(
|
||||
structure.smiles, smirks
|
||||
)
|
||||
|
||||
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)
|
||||
# FAILED (failures=18)
|
||||
|
||||
# TODO mode = "full ambit"
|
||||
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles)
|
||||
# FAILED (failures=46)
|
||||
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(set(ambit_smiles))
|
||||
# FAILED (failures=34)
|
||||
|
||||
# TODO mode = "equality"
|
||||
partial_res = set(ambit_smiles) == set(rdkit_smiles)
|
||||
# FAILED (failures=69)
|
||||
# FAILED (failures=30)
|
||||
|
||||
#
|
||||
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),
|
||||
"url": structure.url,
|
||||
"id": str(structure.uuid),
|
||||
"name": structure.name,
|
||||
"initial_smiles": structure.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']}
|
||||
SMIRKS: {smirks}
|
||||
Compound: {structure.smiles}
|
||||
Compound URL: {structure.url}
|
||||
Num ambit: {len(set(ambit_smiles))}
|
||||
Num rdkit: {len(set(rdkit_smiles))}
|
||||
Num Intersection A: {len(set(ambit_smiles).intersection(set(rdkit_smiles)))}
|
||||
@ -185,15 +215,63 @@ def migration_detail(request, package_uuid, rule_uuid):
|
||||
rdkit_errors: {rdkit_errors}
|
||||
"""
|
||||
|
||||
temp['detail'] = '\n'.join([x.strip() for x in detail.split('\n')])
|
||||
# print(detail.strip())
|
||||
temp["detail"] = "\n".join([x.strip() for x in detail.split("\n")])
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
def compare(request):
|
||||
context = get_base_context(request)
|
||||
|
||||
if request.method == "GET":
|
||||
context["smirks"] = (
|
||||
"[#1,#6:6][#7;X3;!$(NC1CC1)!$([N][C]=O)!$([!#8]CNC=O):1]([#1,#6:7])[#6;A;X4:2][H:3]>>[#1,#6:6][#7;X3:1]([#1,#6:7])[H:3].[#6;A:2]=O"
|
||||
)
|
||||
context["smiles"] = (
|
||||
"C(CC(=O)N[C@@H](CS[Se-])C(=O)NCC(=O)[O-])[C@@H](C(=O)[O-])N"
|
||||
)
|
||||
return render(request, "compare.html", context)
|
||||
|
||||
elif request.method == "POST":
|
||||
smiles = request.POST.get("smiles")
|
||||
smirks = request.POST.get("smirks")
|
||||
|
||||
from envipy_ambit import apply
|
||||
|
||||
ambit_res = apply(smirks, smiles)
|
||||
ambit_res, _ = FormatConverter.sanitize_smiles([str(x) for x in ambit_res])
|
||||
|
||||
products = FormatConverter.apply(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))
|
||||
|
||||
rdkit_res, _ = FormatConverter.sanitize_smiles(all_rdkit_prods)
|
||||
context["result"] = True
|
||||
context["ambit_res"] = sorted(set(ambit_res))
|
||||
context["rdkit_res"] = sorted(set(rdkit_res))
|
||||
context["diff"] = sorted(set(ambit_res).difference(set(rdkit_res)))
|
||||
context["smirks"] = smirks
|
||||
context["smiles"] = smiles
|
||||
|
||||
r = SimpleAmbitRule.objects.filter(smirks=smirks)
|
||||
|
||||
if r.exists():
|
||||
context["rule"] = r.first()
|
||||
|
||||
return render(request, "compare.html", context)
|
||||
|
||||
else:
|
||||
return HttpResponseNotAllowed(["GET", "POST"])
|
||||
|
||||
@ -3,7 +3,7 @@ name = "envipy"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"celery>=5.5.2",
|
||||
"django>=5.2.1",
|
||||
@ -12,9 +12,9 @@ dependencies = [
|
||||
"django-ninja>=1.4.1",
|
||||
"django-oauth-toolkit>=3.0.1",
|
||||
"django-polymorphic>=4.1.0",
|
||||
"django-stubs>=5.2.4",
|
||||
"enviformer",
|
||||
"envipy-additional-information",
|
||||
"envipy-ambit>=0.1.0",
|
||||
"envipy-plugins",
|
||||
"epam-indigo>=1.30.1",
|
||||
"gunicorn>=23.0.0",
|
||||
@ -30,9 +30,62 @@ dependencies = [
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.0" }
|
||||
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.2" }
|
||||
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"}
|
||||
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.7"}
|
||||
envipy-ambit = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" }
|
||||
|
||||
[project.optional-dependencies]
|
||||
ms-login = ["msal>=1.33.0"]
|
||||
dev = [
|
||||
"celery-stubs==0.1.3",
|
||||
"django-stubs>=5.2.4",
|
||||
"poethepoet>=0.37.0",
|
||||
"pre-commit>=4.3.0",
|
||||
"ruff>=0.13.3",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
|
||||
[tool.ruff.lint]
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
[tool.ruff.format]
|
||||
docstring-code-format = true
|
||||
|
||||
# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in selected subdirectories.
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__init__.py" = ["E402"]
|
||||
"**/{tests,docs,tools}/*" = ["E402"]
|
||||
|
||||
[tool.poe.tasks]
|
||||
# Main tasks
|
||||
setup = { sequence = ["db-up", "migrate", "bootstrap"], help = "Complete setup: start database, run migrations, and bootstrap data" }
|
||||
dev = { cmd = "python manage.py runserver", help = "Start the development server", deps = ["db-up"] }
|
||||
|
||||
# Database tasks
|
||||
db-up = { cmd = "docker compose -f docker-compose.dev.yml up -d", help = "Start PostgreSQL database using Docker Compose" }
|
||||
db-down = { cmd = "docker compose -f docker-compose.dev.yml down", help = "Stop PostgreSQL database" }
|
||||
|
||||
# Full cleanup tasks
|
||||
clean = { sequence = ["clean-db"], help = "Remove model files and database volumes (WARNING: destroys all data!)" }
|
||||
clean-db = { cmd = "docker compose -f docker-compose.dev.yml down -v", help = "Removes the database container and volume." }
|
||||
|
||||
# Django tasks
|
||||
migrate = { cmd = "python manage.py migrate", help = "Run database migrations" }
|
||||
bootstrap = { shell = """
|
||||
echo "Bootstrapping initial data..."
|
||||
echo "This will take a bit ⏱️. Get yourself some coffee..."
|
||||
python manage.py bootstrap
|
||||
echo "✓ Bootstrap complete"
|
||||
echo ""
|
||||
echo "Default admin credentials:"
|
||||
echo " Username: admin"
|
||||
echo " Email: admin@envipath.com"
|
||||
echo " Password: SuperSafe"
|
||||
""", help = "Bootstrap initial data (anonymous user, packages, models)" }
|
||||
shell = { cmd = "python manage.py shell", help = "Open Django shell" }
|
||||
|
||||
BIN
static/images/UoA-Logo-Primary-RGB-Large.png
Normal file
BIN
static/images/UoA-Logo-Primary-RGB-Large.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
BIN
static/images/UoA-Logo-Primary-RGB-Reversed-Large.png
Normal file
BIN
static/images/UoA-Logo-Primary-RGB-Reversed-Large.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
BIN
static/images/UoA-Logo-Primary-RGB-Reversed-Small.png
Normal file
BIN
static/images/UoA-Logo-Primary-RGB-Reversed-Small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
static/images/UoA-Logo-Primary-RGB-Small.png
Normal file
BIN
static/images/UoA-Logo-Primary-RGB-Small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
488
static/images/uzh-logo.svg
Normal file
488
static/images/uzh-logo.svg
Normal file
@ -0,0 +1,488 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
|
||||
<svg version="1.1" baseProfile="tiny" id="Universität_Zürich"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="-0.499 -0.501 142.73 49.19" xml:space="preserve">
|
||||
<path d="M6.818,32.721C6.296,32.944,6.2,32.986,6.093,33.04c-0.113,0.058-0.156,0.108-0.133,0.231
|
||||
c0.005,0.029,0.026,0.091,0.047,0.139c0.017,0.038,0.019,0.062-0.006,0.071c-0.023,0.011-0.041-0.01-0.062-0.062
|
||||
c-0.078-0.182-0.161-0.401-0.205-0.506c-0.035-0.082-0.138-0.295-0.191-0.418c-0.021-0.052-0.024-0.079-0.001-0.089
|
||||
c0.024-0.011,0.04,0.007,0.056,0.042c0.016,0.037,0.027,0.057,0.053,0.095c0.057,0.086,0.127,0.088,0.25,0.043
|
||||
c0.113-0.04,0.209-0.081,0.731-0.305l0.481-0.206c0.499-0.215,0.666-0.404,0.729-0.631c0.062-0.21,0.008-0.373-0.043-0.49
|
||||
c-0.063-0.151-0.191-0.315-0.389-0.395c-0.27-0.108-0.584,0.014-0.938,0.166l-0.431,0.184c-0.521,0.225-0.619,0.266-0.726,0.319
|
||||
c-0.114,0.058-0.157,0.107-0.132,0.23c0.005,0.031,0.025,0.091,0.042,0.129c0.016,0.037,0.018,0.062-0.006,0.071
|
||||
c-0.024,0.011-0.041-0.011-0.062-0.06C5.083,31.425,5.001,31.204,5,31.201c-0.018-0.042-0.121-0.254-0.178-0.387
|
||||
c-0.021-0.048-0.024-0.075,0-0.085c0.024-0.012,0.04,0.007,0.057,0.048c0.017,0.038,0.029,0.058,0.054,0.096
|
||||
c0.057,0.085,0.127,0.088,0.25,0.043c0.112-0.04,0.209-0.082,0.73-0.306l0.368-0.157c0.382-0.164,0.803-0.3,1.175-0.117
|
||||
c0.314,0.154,0.458,0.387,0.554,0.608c0.078,0.183,0.21,0.517,0.091,0.865c-0.083,0.242-0.281,0.481-0.78,0.695L6.818,32.721z
|
||||
M6.676,28.899c0.283-0.069,0.368-0.139,0.378-0.217c0.008-0.066-0.004-0.138-0.017-0.195c-0.009-0.04-0.007-0.063,0.017-0.068
|
||||
c0.028-0.006,0.043,0.025,0.055,0.076c0.052,0.237,0.073,0.386,0.088,0.455c0.007,0.033,0.056,0.202,0.098,0.393
|
||||
c0.011,0.048,0.015,0.081-0.019,0.089c-0.022,0.005-0.034-0.017-0.042-0.053c-0.01-0.048-0.028-0.111-0.054-0.16
|
||||
c-0.051-0.089-0.159-0.079-0.479-0.015l-2.169,0.442c-0.073,0.016-0.127,0.016-0.135-0.021c-0.009-0.04,0.031-0.083,0.083-0.16
|
||||
c0.038-0.054,0.511-0.739,0.924-1.39c0.194-0.303,0.612-0.911,0.657-0.986l-0.004-0.018l-1.633,0.396
|
||||
c-0.222,0.053-0.283,0.102-0.301,0.196c-0.01,0.061,0.009,0.146,0.021,0.198c0.01,0.044,0.002,0.061-0.021,0.064
|
||||
c-0.028,0.008-0.042-0.032-0.054-0.088C4.028,27.651,4,27.474,3.984,27.397c-0.009-0.041-0.05-0.178-0.09-0.356
|
||||
c-0.011-0.048-0.016-0.085,0.015-0.091c0.022-0.005,0.038,0.015,0.047,0.059c0.008,0.036,0.015,0.065,0.035,0.106
|
||||
c0.051,0.097,0.135,0.108,0.34,0.067l2.313-0.467c0.08-0.018,0.115-0.01,0.124,0.022c0.009,0.041-0.021,0.094-0.054,0.143
|
||||
c-0.168,0.277-0.544,0.852-0.837,1.312c-0.308,0.484-0.675,0.99-0.729,1.071l0.002,0.011L6.676,28.899z M4.871,25.556
|
||||
c-0.566,0.035-0.672,0.042-0.791,0.058c-0.126,0.016-0.188,0.057-0.201,0.14C3.87,25.796,3.87,25.844,3.873,25.89
|
||||
c0.002,0.036-0.004,0.06-0.034,0.062c-0.021,0.002-0.031-0.027-0.035-0.087c-0.01-0.142-0.014-0.378-0.021-0.486
|
||||
c-0.006-0.093-0.031-0.312-0.04-0.455c-0.003-0.048,0.003-0.079,0.025-0.081c0.029-0.002,0.039,0.021,0.041,0.059
|
||||
c0.002,0.037,0.008,0.066,0.018,0.111c0.025,0.1,0.09,0.126,0.222,0.123c0.12,0,0.225-0.006,0.792-0.042l0.657-0.042
|
||||
c0.362-0.021,0.657-0.041,0.816-0.065c0.1-0.019,0.166-0.048,0.173-0.161c0.004-0.053,0.006-0.135,0.003-0.191
|
||||
c-0.002-0.042,0.007-0.061,0.026-0.062c0.025-0.001,0.039,0.028,0.042,0.069c0.016,0.246,0.02,0.48,0.025,0.583
|
||||
c0.005,0.086,0.031,0.319,0.041,0.47c0.003,0.048-0.006,0.075-0.033,0.076c-0.019,0.002-0.03-0.014-0.033-0.059
|
||||
c-0.003-0.056-0.015-0.1-0.023-0.133c-0.02-0.074-0.084-0.093-0.193-0.097c-0.157-0.009-0.452,0.01-0.814,0.032L4.871,25.556z
|
||||
M4.071,23.877c-0.21,0.073-0.256,0.134-0.302,0.267c-0.02,0.055-0.022,0.114-0.024,0.145c-0.002,0.034-0.014,0.044-0.037,0.043
|
||||
c-0.029-0.002-0.031-0.043-0.026-0.099c0.013-0.198,0.038-0.41,0.045-0.541c0.006-0.093,0.006-0.273,0.019-0.46
|
||||
c0.003-0.045,0.013-0.085,0.039-0.084c0.026,0.002,0.032,0.024,0.03,0.059c-0.003,0.061-0.003,0.116,0.017,0.147
|
||||
c0.018,0.026,0.042,0.041,0.076,0.042c0.049,0.003,0.155-0.024,0.293-0.067l1.736-0.534l0.001-0.015
|
||||
c-0.4-0.188-1.571-0.749-1.812-0.854c-0.047-0.021-0.102-0.041-0.136-0.042c-0.03-0.002-0.061,0.012-0.074,0.044
|
||||
c-0.018,0.045-0.024,0.101-0.027,0.148c-0.003,0.034-0.009,0.063-0.034,0.062c-0.03-0.002-0.035-0.036-0.031-0.106
|
||||
c0.012-0.188,0.033-0.343,0.036-0.391c0.004-0.063,0.004-0.24,0.011-0.353c0.003-0.048,0.013-0.078,0.039-0.076
|
||||
c0.026,0.001,0.032,0.024,0.029,0.062C3.935,21.311,3.93,21.389,3.97,21.46C4,21.51,4.06,21.571,4.293,21.687
|
||||
c0.342,0.168,0.537,0.281,0.983,0.513c0.53,0.274,0.926,0.476,1.106,0.569c0.21,0.11,0.27,0.137,0.266,0.188
|
||||
c-0.003,0.048-0.058,0.067-0.236,0.127L4.071,23.877z M5.128,20.567c-0.556-0.12-0.657-0.143-0.776-0.16
|
||||
c-0.126-0.019-0.191-0.002-0.242,0.113c-0.014,0.026-0.031,0.089-0.042,0.14c-0.009,0.041-0.021,0.062-0.046,0.055
|
||||
c-0.026-0.006-0.027-0.032-0.017-0.087c0.021-0.099,0.047-0.204,0.067-0.295c0.024-0.095,0.045-0.178,0.057-0.229
|
||||
c0.025-0.117,0.183-0.846,0.193-0.916c0.007-0.071,0.013-0.131,0.012-0.162c0-0.02-0.006-0.043-0.002-0.062
|
||||
c0.004-0.018,0.02-0.019,0.038-0.015c0.025,0.005,0.065,0.033,0.231,0.08c0.036,0.012,0.194,0.054,0.236,0.07
|
||||
c0.019,0.008,0.038,0.02,0.032,0.045c-0.005,0.024-0.024,0.028-0.058,0.021C4.787,19.16,4.724,19.15,4.676,19.16
|
||||
c-0.07,0.011-0.122,0.039-0.185,0.216c-0.021,0.062-0.11,0.443-0.126,0.518c-0.004,0.019,0.005,0.027,0.031,0.032l0.925,0.199
|
||||
c0.025,0.005,0.041,0.005,0.045-0.017c0.018-0.081,0.108-0.501,0.119-0.587c0.011-0.089,0.012-0.146-0.018-0.188
|
||||
c-0.023-0.032-0.038-0.051-0.034-0.068c0.003-0.016,0.013-0.024,0.034-0.021c0.022,0.005,0.078,0.032,0.262,0.087
|
||||
c0.072,0.02,0.216,0.062,0.242,0.067C6,19.405,6.04,19.414,6.033,19.447c-0.005,0.026-0.021,0.03-0.04,0.026
|
||||
c-0.037-0.005-0.084-0.015-0.135-0.014c-0.077,0.002-0.144,0.042-0.188,0.174c-0.021,0.068-0.104,0.43-0.123,0.518
|
||||
c-0.004,0.018,0.011,0.024,0.032,0.03l0.289,0.062c0.125,0.027,0.46,0.104,0.567,0.122c0.254,0.047,0.32,0,0.401-0.373
|
||||
c0.021-0.095,0.054-0.249,0.03-0.353c-0.022-0.104-0.091-0.165-0.235-0.224C6.594,19.4,6.581,19.39,6.587,19.364
|
||||
c0.008-0.029,0.036-0.023,0.072-0.015c0.084,0.018,0.327,0.101,0.396,0.135c0.089,0.046,0.083,0.079,0.052,0.218
|
||||
c-0.06,0.274-0.109,0.474-0.146,0.63c-0.041,0.156-0.068,0.27-0.093,0.378c-0.009,0.04-0.026,0.121-0.041,0.209
|
||||
C6.808,21.003,6.796,21.1,6.78,21.173c-0.01,0.049-0.026,0.071-0.052,0.065c-0.019-0.004-0.026-0.021-0.018-0.065
|
||||
c0.013-0.055,0.015-0.1,0.015-0.135c0.001-0.076-0.075-0.112-0.179-0.148c-0.149-0.052-0.438-0.113-0.774-0.187L5.128,20.567z
|
||||
M5.822,17.875c-0.53-0.205-0.628-0.243-0.743-0.278c-0.121-0.039-0.188-0.032-0.257,0.072c-0.018,0.025-0.044,0.083-0.063,0.132
|
||||
c-0.015,0.038-0.029,0.058-0.054,0.048c-0.023-0.01-0.021-0.037-0.002-0.09c0.072-0.185,0.167-0.399,0.195-0.474
|
||||
c0.046-0.118,0.138-0.388,0.18-0.496c0.085-0.22,0.196-0.446,0.398-0.584c0.104-0.072,0.336-0.143,0.569-0.052
|
||||
c0.259,0.1,0.454,0.299,0.604,0.763c0.511-0.16,0.915-0.28,1.21-0.401c0.278-0.117,0.357-0.251,0.389-0.3
|
||||
c0.021-0.035,0.038-0.065,0.048-0.094c0.011-0.028,0.026-0.038,0.044-0.031c0.028,0.012,0.025,0.038,0.01,0.08l-0.128,0.332
|
||||
c-0.075,0.195-0.126,0.276-0.21,0.349c-0.139,0.118-0.354,0.188-0.698,0.279c-0.246,0.065-0.545,0.135-0.615,0.159
|
||||
c-0.027,0.009-0.039,0.029-0.048,0.053l-0.125,0.302c-0.007,0.018-0.004,0.03,0.017,0.039l0.049,0.019
|
||||
c0.325,0.125,0.601,0.232,0.754,0.271C7.45,18,7.535,18.009,7.59,17.91c0.027-0.05,0.064-0.124,0.08-0.166
|
||||
c0.012-0.028,0.027-0.038,0.044-0.031c0.024,0.011,0.025,0.038,0.009,0.083c-0.079,0.202-0.188,0.457-0.209,0.51
|
||||
c-0.025,0.065-0.101,0.289-0.154,0.43C7.341,18.779,7.321,18.8,7.297,18.79c-0.018-0.007-0.021-0.023-0.006-0.065
|
||||
c0.021-0.054,0.029-0.098,0.035-0.132c0.013-0.074-0.057-0.123-0.153-0.176c-0.14-0.074-0.415-0.181-0.735-0.305L5.822,17.875z
|
||||
M6.257,17.57c0.038,0.015,0.056,0.013,0.075-0.007c0.053-0.063,0.104-0.164,0.138-0.251c0.054-0.141,0.058-0.19,0.036-0.271
|
||||
c-0.035-0.134-0.157-0.298-0.443-0.408c-0.495-0.191-0.766,0.081-0.846,0.287c-0.033,0.087-0.055,0.151-0.058,0.19
|
||||
c-0.002,0.027,0.009,0.04,0.037,0.05L6.257,17.57z M8.459,16.014c-0.053,0.038-0.074,0.04-0.146-0.001
|
||||
c-0.18-0.103-0.367-0.227-0.417-0.259c-0.047-0.03-0.077-0.061-0.062-0.087c0.018-0.028,0.048-0.016,0.074-0.001
|
||||
c0.042,0.024,0.118,0.05,0.183,0.064c0.281,0.064,0.479-0.077,0.594-0.278c0.166-0.293,0.049-0.549-0.123-0.646
|
||||
C8.4,14.716,8.223,14.68,7.858,14.83l-0.202,0.083c-0.483,0.199-0.781,0.193-1.044,0.044c-0.357-0.204-0.445-0.647-0.188-1.101
|
||||
c0.12-0.212,0.23-0.33,0.302-0.401c0.022-0.024,0.042-0.036,0.064-0.022c0.042,0.023,0.129,0.091,0.383,0.233
|
||||
c0.072,0.041,0.093,0.065,0.078,0.091c-0.013,0.023-0.038,0.021-0.077,0c-0.028-0.017-0.139-0.058-0.263-0.038
|
||||
c-0.089,0.015-0.241,0.054-0.361,0.265C6.413,14.224,6.47,14.45,6.665,14.56c0.15,0.085,0.307,0.074,0.664-0.079l0.12-0.052
|
||||
c0.521-0.227,0.823-0.238,1.133-0.062c0.188,0.107,0.372,0.306,0.393,0.627c0.012,0.222-0.062,0.421-0.16,0.593
|
||||
C8.706,15.776,8.6,15.909,8.459,16.014z M8.373,12.925c-0.459-0.335-0.544-0.396-0.645-0.462c-0.107-0.067-0.182-0.075-0.244-0.021
|
||||
c-0.034,0.026-0.065,0.063-0.091,0.1c-0.022,0.03-0.042,0.044-0.066,0.026c-0.018-0.013-0.006-0.042,0.029-0.09
|
||||
c0.084-0.115,0.231-0.299,0.296-0.387c0.055-0.075,0.176-0.26,0.26-0.375c0.029-0.039,0.052-0.059,0.07-0.046
|
||||
c0.023,0.019,0.017,0.041-0.005,0.071c-0.022,0.03-0.037,0.057-0.058,0.097c-0.044,0.094-0.011,0.154,0.092,0.237
|
||||
c0.092,0.077,0.177,0.139,0.636,0.474l0.532,0.389c0.293,0.214,0.531,0.388,0.671,0.471c0.088,0.05,0.157,0.069,0.235-0.013
|
||||
c0.037-0.038,0.093-0.101,0.125-0.146c0.024-0.033,0.044-0.042,0.061-0.031c0.021,0.017,0.012,0.047-0.013,0.08
|
||||
c-0.146,0.199-0.294,0.384-0.354,0.466c-0.051,0.068-0.18,0.267-0.269,0.387c-0.029,0.04-0.054,0.054-0.074,0.039
|
||||
c-0.016-0.011-0.017-0.03,0.011-0.066c0.033-0.045,0.053-0.086,0.067-0.118c0.031-0.068-0.007-0.125-0.087-0.197
|
||||
c-0.116-0.108-0.354-0.282-0.648-0.496L8.373,12.925z M9.295,10.406l-0.372,0.415c-0.144,0.163-0.19,0.241-0.17,0.338
|
||||
c0.016,0.065,0.035,0.11,0.052,0.136c0.017,0.026,0.02,0.044,0.004,0.062c-0.019,0.019-0.037,0.012-0.066-0.018
|
||||
c-0.044-0.041-0.242-0.326-0.259-0.353c-0.028-0.042-0.036-0.065-0.021-0.081c0.021-0.022,0.073-0.023,0.144-0.086
|
||||
c0.083-0.071,0.186-0.169,0.271-0.259l1.018-1.076c0.083-0.087,0.131-0.154,0.164-0.2c0.029-0.049,0.045-0.076,0.056-0.086
|
||||
c0.018-0.019,0.037-0.006,0.078,0.032c0.057,0.054,0.237,0.238,0.308,0.306c0.024,0.028,0.036,0.049,0.021,0.065
|
||||
c-0.021,0.021-0.039,0.014-0.083-0.021l-0.032-0.024c-0.076-0.062-0.221-0.061-0.456,0.177l-0.332,0.335l1.115,1.053
|
||||
c0.25,0.236,0.465,0.439,0.595,0.536c0.083,0.062,0.159,0.104,0.247,0.032c0.042-0.033,0.104-0.087,0.142-0.128
|
||||
c0.028-0.03,0.05-0.036,0.062-0.023c0.02,0.019,0.007,0.047-0.021,0.077c-0.169,0.18-0.339,0.343-0.41,0.419
|
||||
c-0.06,0.062-0.214,0.242-0.315,0.351c-0.033,0.035-0.06,0.047-0.079,0.028c-0.014-0.013-0.012-0.031,0.02-0.063
|
||||
c0.038-0.041,0.063-0.078,0.081-0.108c0.04-0.065-0.004-0.138-0.074-0.22c-0.103-0.122-0.316-0.324-0.566-0.562L9.295,10.406z
|
||||
M12.134,9.947c-0.015,0.012-0.016,0.021-0.008,0.044l0.176,0.544c0.029,0.096,0.07,0.178,0.099,0.213
|
||||
c0.042,0.053,0.099,0.069,0.188-0.003l0.044-0.035c0.035-0.027,0.049-0.028,0.062-0.012c0.019,0.023,0.006,0.043-0.026,0.069
|
||||
c-0.094,0.074-0.227,0.166-0.316,0.237c-0.032,0.026-0.187,0.163-0.338,0.284c-0.038,0.03-0.061,0.039-0.079,0.016
|
||||
c-0.015-0.019-0.007-0.033,0.017-0.052c0.026-0.021,0.065-0.057,0.086-0.078c0.121-0.125,0.097-0.269,0.039-0.461l-0.73-2.421
|
||||
c-0.032-0.112-0.041-0.159-0.012-0.182c0.026-0.021,0.065-0.009,0.147,0.035c0.199,0.104,1.618,0.924,2.159,1.22
|
||||
c0.32,0.174,0.438,0.148,0.512,0.113c0.051-0.026,0.097-0.059,0.132-0.086c0.023-0.02,0.041-0.027,0.057-0.008
|
||||
c0.02,0.023-0.003,0.051-0.11,0.138c-0.105,0.084-0.319,0.254-0.558,0.435c-0.055,0.039-0.09,0.067-0.105,0.047
|
||||
c-0.014-0.018-0.007-0.032,0.02-0.059c0.017-0.022,0.016-0.065-0.026-0.088l-0.729-0.434c-0.017-0.01-0.031-0.009-0.045,0.003
|
||||
L12.134,9.947z M12.614,9.325c0.014-0.012,0.01-0.022,0-0.029l-0.839-0.513c-0.012-0.009-0.027-0.021-0.036-0.015
|
||||
c-0.009,0.007-0.003,0.025,0.003,0.041l0.305,0.934c0.008,0.014,0.018,0.021,0.028,0.011L12.614,9.325z M14.557,9.301
|
||||
c-0.064,0.009-0.084-0.002-0.126-0.072c-0.104-0.178-0.206-0.378-0.233-0.432c-0.025-0.05-0.037-0.091-0.011-0.105
|
||||
c0.028-0.018,0.049,0.01,0.063,0.036c0.024,0.042,0.078,0.102,0.126,0.146c0.212,0.196,0.453,0.17,0.652,0.052
|
||||
c0.29-0.172,0.315-0.451,0.214-0.623c-0.093-0.156-0.229-0.276-0.62-0.327l-0.217-0.028c-0.518-0.067-0.773-0.22-0.928-0.48
|
||||
c-0.21-0.354-0.064-0.782,0.383-1.048c0.209-0.124,0.363-0.172,0.46-0.199c0.033-0.011,0.055-0.011,0.068,0.013
|
||||
c0.024,0.042,0.066,0.143,0.216,0.394c0.042,0.071,0.047,0.104,0.021,0.118c-0.021,0.013-0.043,0-0.065-0.039
|
||||
c-0.018-0.029-0.093-0.119-0.209-0.163c-0.084-0.032-0.235-0.074-0.444,0.05c-0.238,0.142-0.301,0.365-0.187,0.559
|
||||
c0.088,0.147,0.229,0.217,0.614,0.261l0.13,0.014c0.562,0.062,0.833,0.202,1.015,0.508c0.109,0.187,0.171,0.45,0.028,0.738
|
||||
c-0.1,0.198-0.263,0.333-0.433,0.436C14.89,9.217,14.731,9.28,14.557,9.301z M34.089,6.584L33.6,6.314
|
||||
c-0.191-0.104-0.278-0.133-0.368-0.09c-0.062,0.028-0.101,0.059-0.121,0.081c-0.022,0.021-0.039,0.028-0.06,0.018
|
||||
c-0.021-0.013-0.02-0.033,0.002-0.069c0.029-0.051,0.264-0.309,0.285-0.332c0.034-0.036,0.056-0.05,0.075-0.038
|
||||
c0.026,0.016,0.039,0.066,0.116,0.12c0.088,0.064,0.207,0.144,0.312,0.205l1.278,0.75c0.104,0.062,0.18,0.093,0.232,0.115
|
||||
c0.054,0.019,0.084,0.027,0.097,0.035c0.022,0.014,0.015,0.035-0.014,0.083c-0.04,0.067-0.181,0.285-0.229,0.369
|
||||
c-0.021,0.03-0.04,0.046-0.06,0.035c-0.025-0.016-0.022-0.035,0.003-0.086l0.018-0.036c0.042-0.089,0.008-0.229-0.274-0.404
|
||||
l-0.401-0.25l-0.776,1.323c-0.174,0.297-0.323,0.552-0.39,0.699c-0.043,0.097-0.064,0.179,0.024,0.248
|
||||
c0.042,0.033,0.108,0.081,0.156,0.109c0.036,0.021,0.047,0.04,0.037,0.056c-0.013,0.022-0.044,0.018-0.08-0.004
|
||||
C33.25,9.129,33.053,9,32.962,8.947c-0.073-0.043-0.282-0.153-0.412-0.229c-0.042-0.023-0.059-0.048-0.046-0.069
|
||||
c0.01-0.017,0.028-0.019,0.067,0.004c0.048,0.029,0.091,0.045,0.124,0.055c0.072,0.025,0.133-0.034,0.197-0.122
|
||||
c0.096-0.126,0.245-0.381,0.42-0.678L34.089,6.584z M35.774,8.711c0.371-0.431,0.438-0.511,0.511-0.605
|
||||
c0.077-0.102,0.093-0.166,0.017-0.267c-0.018-0.024-0.062-0.069-0.104-0.104c-0.031-0.027-0.043-0.047-0.026-0.067
|
||||
c0.018-0.02,0.042-0.009,0.084,0.028c0.15,0.129,0.322,0.291,0.407,0.364c0.068,0.058,0.254,0.204,0.356,0.291
|
||||
c0.042,0.037,0.058,0.059,0.04,0.079c-0.018,0.02-0.039,0.011-0.067-0.014c-0.031-0.026-0.051-0.039-0.09-0.062
|
||||
c-0.089-0.053-0.153-0.022-0.244,0.071c-0.084,0.087-0.152,0.166-0.521,0.598l-0.34,0.396c-0.353,0.412-0.419,0.655-0.378,0.889
|
||||
c0.038,0.214,0.158,0.338,0.255,0.421c0.125,0.106,0.312,0.198,0.523,0.183c0.29-0.021,0.518-0.271,0.769-0.562l0.306-0.354
|
||||
c0.37-0.432,0.438-0.511,0.511-0.605c0.077-0.103,0.093-0.167,0.017-0.268c-0.018-0.024-0.062-0.068-0.095-0.097
|
||||
C37.673,9,37.661,8.979,37.678,8.96c0.017-0.02,0.042-0.009,0.08,0.025c0.146,0.124,0.316,0.286,0.319,0.289
|
||||
c0.034,0.028,0.22,0.174,0.331,0.269c0.04,0.034,0.055,0.058,0.038,0.077c-0.017,0.021-0.039,0.011-0.073-0.018
|
||||
c-0.031-0.027-0.05-0.039-0.089-0.062c-0.089-0.052-0.153-0.022-0.244,0.071c-0.083,0.086-0.151,0.166-0.521,0.597l-0.262,0.303
|
||||
c-0.27,0.315-0.589,0.623-1.002,0.623c-0.35,0-0.581-0.144-0.766-0.302c-0.15-0.129-0.416-0.372-0.462-0.737
|
||||
c-0.033-0.254,0.04-0.557,0.393-0.969L35.774,8.711z M38.61,11.381c0.443-0.355,0.523-0.422,0.613-0.503
|
||||
c0.094-0.085,0.122-0.146,0.064-0.259c-0.013-0.028-0.05-0.08-0.083-0.121c-0.025-0.032-0.034-0.054-0.014-0.071
|
||||
c0.021-0.016,0.043,0,0.078,0.043c0.125,0.154,0.263,0.346,0.312,0.406c0.079,0.1,0.268,0.313,0.339,0.404
|
||||
c0.148,0.184,0.288,0.393,0.308,0.637c0.01,0.126-0.045,0.362-0.24,0.521c-0.216,0.173-0.486,0.242-0.962,0.141
|
||||
c-0.117,0.522-0.215,0.932-0.257,1.249c-0.038,0.299,0.038,0.436,0.063,0.485c0.021,0.037,0.038,0.064,0.058,0.089
|
||||
c0.019,0.022,0.02,0.042,0.005,0.054c-0.023,0.02-0.045,0.003-0.074-0.032l-0.223-0.275c-0.132-0.163-0.176-0.248-0.195-0.356
|
||||
c-0.034-0.179,0.014-0.399,0.106-0.744c0.066-0.246,0.156-0.539,0.171-0.611c0.006-0.029-0.007-0.05-0.022-0.069l-0.198-0.258
|
||||
c-0.012-0.016-0.023-0.02-0.042-0.005l-0.041,0.032c-0.271,0.219-0.501,0.403-0.611,0.517c-0.077,0.077-0.127,0.146-0.068,0.243
|
||||
c0.029,0.048,0.075,0.117,0.103,0.151c0.02,0.023,0.021,0.042,0.006,0.055c-0.021,0.016-0.046,0.003-0.076-0.035
|
||||
c-0.137-0.169-0.301-0.393-0.336-0.437c-0.044-0.055-0.201-0.23-0.294-0.348c-0.031-0.038-0.038-0.065-0.019-0.082
|
||||
c0.016-0.012,0.033-0.007,0.062,0.028c0.034,0.043,0.068,0.073,0.096,0.096c0.059,0.049,0.135,0.012,0.229-0.044
|
||||
c0.135-0.084,0.364-0.271,0.633-0.485L38.61,11.381z M38.656,11.91c-0.032,0.026-0.039,0.041-0.031,0.069
|
||||
c0.029,0.078,0.09,0.172,0.149,0.245c0.094,0.116,0.136,0.145,0.216,0.167c0.134,0.035,0.336,0.013,0.575-0.181
|
||||
c0.414-0.333,0.312-0.703,0.174-0.875c-0.059-0.072-0.104-0.123-0.136-0.145c-0.022-0.017-0.038-0.012-0.062,0.006L38.656,11.91z
|
||||
M40.591,14.113c0.49-0.289,0.58-0.342,0.68-0.409c0.104-0.071,0.142-0.136,0.116-0.216c-0.012-0.041-0.033-0.084-0.056-0.123
|
||||
c-0.02-0.032-0.023-0.056,0.001-0.071c0.021-0.011,0.041,0.012,0.072,0.062c0.072,0.123,0.182,0.331,0.237,0.424
|
||||
c0.047,0.081,0.169,0.265,0.241,0.387c0.024,0.042,0.033,0.072,0.015,0.083c-0.025,0.016-0.043,0-0.062-0.033
|
||||
c-0.019-0.032-0.038-0.056-0.065-0.091c-0.067-0.078-0.139-0.07-0.254-0.011c-0.107,0.054-0.197,0.108-0.688,0.396l-0.566,0.334
|
||||
c-0.312,0.185-0.567,0.335-0.698,0.43c-0.081,0.061-0.126,0.118-0.082,0.222c0.021,0.049,0.056,0.123,0.084,0.172
|
||||
c0.021,0.035,0.021,0.058,0.004,0.066c-0.022,0.014-0.047-0.007-0.068-0.042c-0.125-0.212-0.234-0.421-0.287-0.508
|
||||
c-0.043-0.074-0.173-0.271-0.249-0.4c-0.024-0.042-0.027-0.07-0.006-0.083c0.017-0.009,0.033-0.002,0.057,0.036
|
||||
c0.028,0.049,0.058,0.083,0.082,0.109c0.051,0.057,0.116,0.043,0.216-0.002c0.145-0.063,0.398-0.214,0.712-0.397L40.591,14.113z
|
||||
M40.314,16.493c0.157-0.461,0.507-0.711,0.842-0.85c0.235-0.098,0.672-0.193,1.114,0.013c0.331,0.154,0.604,0.425,0.817,0.94
|
||||
c0.089,0.214,0.128,0.348,0.174,0.515c0.036,0.139,0.052,0.262,0.084,0.37c0.012,0.039,0.001,0.06-0.021,0.068
|
||||
c-0.028,0.012-0.073,0.019-0.197,0.062c-0.116,0.041-0.306,0.123-0.378,0.144c-0.053,0.019-0.084,0.022-0.097-0.008
|
||||
c-0.011-0.028,0.014-0.046,0.062-0.065c0.105-0.048,0.208-0.144,0.263-0.267c0.073-0.164,0.056-0.472-0.076-0.79
|
||||
c-0.125-0.3-0.282-0.466-0.483-0.561c-0.335-0.156-0.69-0.086-1.025,0.053c-0.822,0.34-0.972,1.134-0.78,1.597
|
||||
c0.127,0.309,0.239,0.48,0.455,0.554c0.09,0.03,0.209,0.034,0.275,0.022c0.061-0.012,0.076-0.011,0.089,0.017
|
||||
c0.01,0.023-0.012,0.042-0.039,0.053c-0.041,0.017-0.359,0.1-0.491,0.114c-0.066,0.007-0.089,0-0.137-0.05
|
||||
c-0.113-0.114-0.246-0.367-0.338-0.589C40.232,17.361,40.171,16.92,40.314,16.493z M42.775,19.295
|
||||
c0.555-0.125,0.657-0.146,0.771-0.181c0.123-0.035,0.175-0.077,0.174-0.204c0-0.031-0.01-0.094-0.021-0.145
|
||||
c-0.009-0.04-0.007-0.064,0.019-0.069c0.026-0.006,0.039,0.018,0.052,0.073c0.021,0.099,0.042,0.205,0.062,0.296
|
||||
c0.019,0.096,0.033,0.181,0.044,0.23c0.026,0.117,0.189,0.844,0.208,0.912c0.023,0.068,0.043,0.125,0.058,0.152
|
||||
c0.008,0.018,0.023,0.037,0.027,0.056s-0.01,0.025-0.027,0.029c-0.025,0.005-0.073-0.003-0.244,0.023
|
||||
c-0.038,0.005-0.2,0.033-0.245,0.036c-0.02,0-0.042-0.002-0.048-0.026c-0.006-0.025,0.011-0.037,0.043-0.044
|
||||
c0.025-0.005,0.087-0.023,0.126-0.052c0.06-0.041,0.096-0.087,0.077-0.273c-0.006-0.063-0.085-0.448-0.103-0.521
|
||||
c-0.004-0.018-0.017-0.022-0.042-0.017l-0.923,0.208c-0.026,0.006-0.04,0.013-0.035,0.034c0.019,0.081,0.112,0.5,0.14,0.583
|
||||
c0.026,0.086,0.05,0.138,0.094,0.163c0.035,0.019,0.056,0.028,0.061,0.047c0.003,0.015-0.001,0.026-0.022,0.032
|
||||
c-0.022,0.005-0.083,0.003-0.273,0.03c-0.074,0.014-0.223,0.035-0.248,0.041c-0.028,0.006-0.068,0.017-0.077-0.018
|
||||
c-0.006-0.025,0.008-0.036,0.025-0.04c0.036-0.012,0.083-0.022,0.128-0.044c0.069-0.035,0.112-0.099,0.096-0.236
|
||||
c-0.008-0.071-0.086-0.434-0.104-0.521c-0.004-0.019-0.021-0.02-0.042-0.015l-0.288,0.065c-0.124,0.028-0.461,0.1-0.566,0.127
|
||||
c-0.25,0.062-0.292,0.134-0.208,0.507c0.021,0.095,0.057,0.248,0.121,0.333c0.065,0.085,0.152,0.112,0.308,0.104
|
||||
c0.042-0.001,0.058,0.002,0.063,0.027c0.006,0.029-0.022,0.036-0.06,0.044c-0.084,0.02-0.338,0.045-0.416,0.043
|
||||
c-0.102-0.004-0.108-0.037-0.14-0.176c-0.062-0.272-0.099-0.476-0.131-0.634c-0.027-0.158-0.05-0.272-0.074-0.382
|
||||
c-0.009-0.04-0.026-0.121-0.05-0.207c-0.019-0.084-0.047-0.177-0.063-0.25c-0.012-0.047-0.006-0.075,0.02-0.081
|
||||
c0.019-0.004,0.033,0.008,0.042,0.052c0.012,0.055,0.029,0.097,0.044,0.128c0.031,0.069,0.115,0.07,0.226,0.062
|
||||
c0.156-0.017,0.445-0.081,0.78-0.156L42.775,19.295z M42.087,22.487c-0.292,0.018-0.387,0.07-0.41,0.146
|
||||
c-0.021,0.064-0.021,0.137-0.02,0.195c0.001,0.041-0.005,0.064-0.027,0.064c-0.029,0.001-0.039-0.032-0.041-0.084
|
||||
c-0.009-0.243-0.003-0.394-0.005-0.465c-0.001-0.033-0.019-0.209-0.024-0.402c-0.002-0.048,0.001-0.083,0.033-0.083
|
||||
c0.022-0.001,0.031,0.021,0.032,0.059c0.002,0.049,0.008,0.116,0.024,0.167c0.034,0.097,0.143,0.107,0.468,0.101l2.214-0.044
|
||||
c0.075-0.003,0.128,0.007,0.129,0.044c0.001,0.041-0.046,0.075-0.11,0.143c-0.048,0.046-0.637,0.636-1.159,1.201
|
||||
c-0.245,0.263-0.767,0.787-0.823,0.852v0.02l1.677-0.098c0.229-0.012,0.298-0.048,0.332-0.139c0.021-0.057,0.018-0.146,0.016-0.199
|
||||
c-0.001-0.045,0.009-0.061,0.031-0.061c0.03-0.001,0.035,0.04,0.037,0.095c0.007,0.194,0.002,0.375,0.005,0.453
|
||||
c0.001,0.041,0.019,0.183,0.024,0.366c0.001,0.049,0,0.086-0.031,0.087c-0.021,0.001-0.034-0.021-0.036-0.066
|
||||
c-0.001-0.038-0.002-0.066-0.015-0.111c-0.034-0.104-0.113-0.132-0.323-0.127l-2.359,0.042c-0.082,0.003-0.116-0.011-0.118-0.044
|
||||
c-0.001-0.041,0.038-0.088,0.078-0.13c0.216-0.243,0.688-0.738,1.06-1.142c0.39-0.421,0.842-0.853,0.911-0.923v-0.011L42.087,22.487
|
||||
z M41.585,25.938c0.035-0.057,0.056-0.064,0.137-0.054c0.203,0.03,0.424,0.076,0.481,0.09c0.055,0.012,0.094,0.027,0.09,0.058
|
||||
c-0.004,0.034-0.038,0.032-0.067,0.028c-0.049-0.008-0.128-0.003-0.194,0.006c-0.286,0.042-0.417,0.246-0.45,0.476
|
||||
c-0.048,0.333,0.154,0.528,0.352,0.558c0.182,0.025,0.359-0.006,0.645-0.277l0.158-0.151c0.377-0.36,0.656-0.465,0.956-0.422
|
||||
c0.407,0.06,0.649,0.441,0.577,0.956c-0.035,0.24-0.095,0.392-0.135,0.483c-0.012,0.031-0.025,0.049-0.052,0.045
|
||||
c-0.048-0.007-0.152-0.037-0.441-0.078c-0.082-0.012-0.109-0.027-0.105-0.057c0.004-0.026,0.027-0.034,0.072-0.027
|
||||
c0.033,0.004,0.149,0.002,0.258-0.062c0.078-0.046,0.205-0.137,0.239-0.378c0.04-0.272-0.097-0.463-0.318-0.495
|
||||
c-0.17-0.023-0.312,0.042-0.589,0.316l-0.093,0.093c-0.401,0.398-0.68,0.521-1.031,0.472c-0.215-0.03-0.458-0.148-0.594-0.44
|
||||
c-0.092-0.202-0.097-0.414-0.067-0.61C41.442,26.249,41.492,26.087,41.585,25.938z M42.705,29.112
|
||||
c0.547,0.154,0.648,0.183,0.766,0.208c0.125,0.027,0.195,0.009,0.235-0.065c0.022-0.036,0.039-0.082,0.051-0.125
|
||||
c0.011-0.036,0.023-0.056,0.053-0.048c0.021,0.007,0.021,0.037,0.004,0.095c-0.039,0.138-0.113,0.36-0.144,0.466
|
||||
c-0.024,0.09-0.073,0.305-0.112,0.44c-0.014,0.047-0.028,0.074-0.051,0.068c-0.028-0.009-0.029-0.032-0.02-0.068
|
||||
s0.015-0.064,0.02-0.11c0.01-0.103-0.044-0.147-0.168-0.19c-0.112-0.04-0.213-0.068-0.76-0.223l-0.633-0.181
|
||||
c-0.349-0.099-0.633-0.179-0.792-0.208c-0.1-0.017-0.172-0.01-0.217,0.095c-0.021,0.049-0.051,0.126-0.066,0.18
|
||||
c-0.011,0.04-0.026,0.055-0.045,0.05c-0.025-0.007-0.028-0.039-0.017-0.079c0.066-0.236,0.142-0.461,0.169-0.559
|
||||
c0.022-0.082,0.077-0.312,0.118-0.456c0.013-0.047,0.03-0.068,0.056-0.062c0.018,0.005,0.025,0.022,0.013,0.066
|
||||
c-0.016,0.053-0.021,0.099-0.021,0.133c-0.006,0.076,0.049,0.115,0.149,0.155c0.146,0.061,0.431,0.141,0.779,0.239L42.705,29.112z
|
||||
M40.502,30.431c0.048-0.046,0.069-0.048,0.146-0.016c0.188,0.081,0.389,0.186,0.441,0.211c0.051,0.026,0.083,0.054,0.072,0.08
|
||||
c-0.013,0.031-0.046,0.021-0.073,0.009c-0.044-0.019-0.123-0.035-0.188-0.044c-0.287-0.033-0.467,0.128-0.56,0.342
|
||||
c-0.134,0.309,0.011,0.55,0.192,0.629c0.168,0.071,0.348,0.09,0.694-0.101l0.191-0.104c0.458-0.25,0.755-0.277,1.033-0.156
|
||||
c0.377,0.163,0.512,0.596,0.306,1.072c-0.097,0.224-0.192,0.353-0.256,0.432c-0.02,0.028-0.038,0.041-0.062,0.03
|
||||
c-0.044-0.021-0.139-0.076-0.406-0.192c-0.075-0.032-0.099-0.055-0.086-0.082c0.01-0.022,0.035-0.024,0.077-0.007
|
||||
c0.031,0.013,0.145,0.042,0.265,0.009c0.088-0.022,0.233-0.079,0.33-0.302c0.11-0.254,0.029-0.473-0.177-0.562
|
||||
c-0.158-0.068-0.312-0.041-0.651,0.149l-0.113,0.064c-0.492,0.28-0.792,0.325-1.119,0.185c-0.199-0.086-0.403-0.264-0.458-0.58
|
||||
c-0.036-0.22,0.017-0.425,0.095-0.606C40.282,30.694,40.373,30.55,40.502,30.431z M13.791,41.773c-0.024,0-0.056-0.002-0.114-0.02
|
||||
c-0.053-0.015-0.07-0.058-0.09-0.186l-0.189-1.25c-0.009-0.048-0.034-0.077-0.064-0.079c-0.029,0.001-0.052,0.024-0.069,0.06
|
||||
l-0.548,1.112l-0.546-1.098c-0.026-0.057-0.05-0.074-0.08-0.074c-0.029,0.001-0.052,0.029-0.057,0.066l-0.207,1.321
|
||||
c-0.009,0.067-0.032,0.141-0.071,0.142c-0.032,0.004-0.046,0.004-0.062,0.004c-0.025,0-0.049,0.012-0.05,0.037
|
||||
c0,0.032,0.033,0.04,0.055,0.04c0.068,0,0.168-0.008,0.208-0.008c0.037,0,0.132,0.008,0.221,0.008c0.029,0,0.065-0.005,0.067-0.04
|
||||
c-0.003-0.027-0.026-0.036-0.05-0.037c-0.018,0-0.035-0.002-0.07-0.009c-0.035-0.009-0.05-0.017-0.051-0.044
|
||||
c0-0.031,0.002-0.058,0.007-0.093l0.094-0.746c0.07,0.145,0.175,0.359,0.19,0.395c0.024,0.06,0.19,0.367,0.241,0.461
|
||||
c0.034,0.061,0.054,0.105,0.099,0.111c0.04-0.004,0.051-0.031,0.099-0.124l0.432-0.866l0.109,0.832
|
||||
c0.002,0.018,0.003,0.032,0.003,0.043c0,0.023-0.003,0.022-0.002,0.024c-0.015,0.006-0.033,0.015-0.035,0.037
|
||||
c0.003,0.029,0.029,0.039,0.078,0.041c0.084,0.005,0.382,0.015,0.436,0.015c0.031-0.001,0.066-0.003,0.071-0.04
|
||||
C13.839,41.781,13.812,41.773,13.791,41.773z M15.5,40.521c-0.234-0.237-0.593-0.235-0.854-0.235c-0.126,0-0.277,0.004-0.341,0.004
|
||||
c-0.06,0-0.193-0.004-0.303-0.004c-0.028,0-0.062,0.002-0.064,0.035c0.001,0.029,0.029,0.039,0.053,0.039
|
||||
c0.03,0,0.066,0.002,0.079,0.006c0.063,0.021,0.071,0.028,0.078,0.095c0.003,0.063,0.003,0.12,0.003,0.429v0.355
|
||||
c0,0.188,0,0.346-0.01,0.43c-0.009,0.061-0.019,0.086-0.05,0.094c-0.018,0.004-0.04,0.006-0.069,0.006
|
||||
c-0.031,0-0.05,0.02-0.05,0.039c0.001,0.028,0.029,0.038,0.058,0.038c0.043,0,0.096-0.002,0.146-0.005
|
||||
c0.051,0,0.097-0.003,0.119-0.003c0.052,0,0.127,0.006,0.21,0.013c0.083,0.005,0.175,0.012,0.251,0.012
|
||||
c0.391,0,0.616-0.145,0.715-0.243c0.121-0.119,0.233-0.315,0.233-0.576C15.703,40.802,15.607,40.629,15.5,40.521z M15.368,41.105
|
||||
c0,0.207-0.042,0.382-0.172,0.488c-0.123,0.103-0.258,0.135-0.446,0.135c-0.162,0.001-0.24-0.044-0.26-0.073
|
||||
c-0.011-0.012-0.02-0.086-0.021-0.134c-0.003-0.04-0.006-0.195-0.006-0.409v-0.255c0-0.159,0-0.326,0.003-0.396
|
||||
c0.003-0.021,0-0.018,0.016-0.024c0.009-0.007,0.082-0.015,0.123-0.014c0.163,0,0.386,0.021,0.574,0.188
|
||||
C15.268,40.69,15.368,40.853,15.368,41.105z M17.351,41.434c-0.028-0.001-0.041,0.025-0.042,0.055
|
||||
c-0.006,0.033-0.034,0.09-0.068,0.127c-0.078,0.087-0.173,0.102-0.358,0.102c-0.272-0.001-0.635-0.222-0.636-0.691
|
||||
c0-0.196,0.038-0.386,0.188-0.514c0.091-0.077,0.203-0.11,0.384-0.11c0.189,0,0.327,0.05,0.393,0.114
|
||||
c0.049,0.049,0.074,0.116,0.076,0.177c0,0.023,0.004,0.058,0.039,0.06c0.036-0.003,0.043-0.038,0.045-0.065
|
||||
c0.002-0.041,0.002-0.153,0.007-0.22c0.004-0.07,0.01-0.094,0.01-0.112c0.002-0.021-0.02-0.04-0.047-0.04
|
||||
c-0.061-0.008-0.129-0.022-0.208-0.034c-0.096-0.012-0.176-0.021-0.308-0.021c-0.313,0-0.52,0.083-0.675,0.22
|
||||
c-0.205,0.184-0.251,0.426-0.251,0.566c0,0.198,0.056,0.435,0.268,0.61c0.195,0.164,0.442,0.224,0.73,0.224
|
||||
c0.137,0,0.296-0.012,0.385-0.047c0.037-0.013,0.057-0.033,0.065-0.069c0.021-0.072,0.045-0.245,0.045-0.273
|
||||
C17.391,41.465,17.382,41.437,17.351,41.434z M19.029,41.434c-0.028-0.001-0.041,0.025-0.043,0.055
|
||||
c-0.005,0.033-0.034,0.09-0.067,0.127c-0.079,0.087-0.173,0.102-0.358,0.102c-0.273-0.001-0.636-0.222-0.637-0.691
|
||||
c0-0.196,0.038-0.386,0.188-0.514c0.092-0.077,0.203-0.11,0.384-0.11c0.19,0,0.327,0.05,0.394,0.114
|
||||
c0.049,0.049,0.072,0.116,0.075,0.177c0,0.023,0.006,0.058,0.039,0.06c0.037-0.003,0.043-0.038,0.045-0.065
|
||||
c0.002-0.041,0.002-0.153,0.008-0.22c0.004-0.07,0.009-0.094,0.01-0.112c0.001-0.021-0.021-0.04-0.047-0.04
|
||||
c-0.062-0.008-0.129-0.022-0.208-0.034c-0.097-0.012-0.177-0.021-0.308-0.021c-0.314,0-0.521,0.083-0.676,0.22
|
||||
c-0.205,0.184-0.251,0.426-0.251,0.566c0,0.198,0.056,0.435,0.269,0.61c0.194,0.164,0.441,0.224,0.729,0.224
|
||||
c0.137,0,0.297-0.012,0.385-0.047c0.037-0.013,0.058-0.033,0.064-0.069c0.021-0.072,0.045-0.245,0.046-0.273
|
||||
C19.069,41.465,19.06,41.437,19.029,41.434z M20.707,41.434c-0.027-0.001-0.041,0.025-0.043,0.055
|
||||
c-0.006,0.033-0.034,0.09-0.066,0.127c-0.079,0.087-0.174,0.102-0.359,0.102c-0.272-0.001-0.635-0.222-0.635-0.691
|
||||
c0-0.196,0.037-0.386,0.187-0.514c0.092-0.077,0.203-0.11,0.385-0.11c0.189,0,0.327,0.05,0.394,0.114
|
||||
c0.049,0.049,0.074,0.116,0.076,0.177c0,0.023,0.005,0.058,0.039,0.06c0.037-0.003,0.043-0.038,0.045-0.065
|
||||
c0.002-0.041,0.002-0.153,0.007-0.22c0.005-0.07,0.009-0.094,0.009-0.112c0.002-0.021-0.019-0.04-0.046-0.04
|
||||
c-0.061-0.008-0.129-0.022-0.209-0.034c-0.095-0.012-0.175-0.021-0.307-0.021c-0.314,0-0.521,0.083-0.677,0.22
|
||||
c-0.205,0.184-0.251,0.426-0.251,0.566c0,0.198,0.057,0.435,0.269,0.61c0.194,0.164,0.441,0.224,0.729,0.224
|
||||
c0.138,0,0.297-0.012,0.386-0.047c0.036-0.013,0.057-0.033,0.064-0.069c0.022-0.072,0.045-0.245,0.046-0.273
|
||||
C20.747,41.465,20.738,41.437,20.707,41.434z M28.803,41.773c-0.019,0-0.051-0.002-0.076-0.011
|
||||
c-0.043-0.015-0.072-0.032-0.106-0.075c-0.049-0.061-0.379-0.581-0.459-0.703l0.34-0.465c0.064-0.089,0.104-0.141,0.142-0.148
|
||||
c0.027-0.007,0.053-0.011,0.071-0.011c0.025,0,0.05-0.014,0.051-0.039c-0.002-0.031-0.032-0.036-0.056-0.036
|
||||
c-0.08,0-0.166,0.005-0.205,0.005c-0.04,0-0.138-0.005-0.22-0.005c-0.029,0-0.06,0.005-0.062,0.036
|
||||
c0.001,0.028,0.025,0.039,0.043,0.039c0.017,0,0.044,0,0.062,0.006c0.019,0.004,0.024,0.015,0.024,0.018
|
||||
c0,0.013-0.006,0.039-0.022,0.064c-0.032,0.053-0.195,0.293-0.268,0.399c-0.079-0.132-0.161-0.266-0.248-0.418
|
||||
c-0.011-0.02-0.021-0.047-0.021-0.052c0-0.002,0.002-0.009,0.017-0.013s0.036-0.006,0.046-0.006c0.021,0,0.049-0.01,0.05-0.039
|
||||
c-0.002-0.031-0.033-0.036-0.06-0.036c-0.079,0-0.208,0.005-0.236,0.005c-0.101,0-0.269-0.005-0.319-0.005
|
||||
c-0.024,0.001-0.055,0.001-0.06,0.034c0,0.021,0.016,0.041,0.038,0.041c0.019,0,0.051,0.004,0.083,0.014
|
||||
c0.068,0.022,0.106,0.062,0.16,0.142l0.361,0.564l-0.401,0.544c-0.072,0.099-0.102,0.125-0.158,0.142
|
||||
c-0.028,0.007-0.06,0.009-0.072,0.009c-0.024,0.001-0.045,0.018-0.045,0.041s0.024,0.036,0.048,0.036h0.036
|
||||
c0.034,0,0.137-0.008,0.176-0.008c0.052,0,0.189,0.008,0.203,0.008h0.038c0.027,0,0.058-0.004,0.06-0.036
|
||||
c-0.002-0.025-0.021-0.039-0.043-0.041c-0.015,0-0.03-0.002-0.045-0.002c-0.013,0-0.022-0.01-0.022-0.018c0-0.001,0-0.003,0-0.003
|
||||
c0-0.015,0.01-0.042,0.028-0.07l0.293-0.453c0.093,0.149,0.201,0.332,0.316,0.52c0.004,0.007,0.005,0.012,0.005,0.014
|
||||
c0,0.005-0.002,0.005-0.002,0.005c-0.021,0.005-0.041,0.021-0.041,0.043c0.006,0.036,0.038,0.034,0.09,0.038
|
||||
c0.172,0.005,0.339,0.005,0.39,0.005h0.062c0.024,0,0.055-0.008,0.059-0.038C28.846,41.788,28.824,41.773,28.803,41.773z
|
||||
M30.581,41.773c-0.018,0-0.05-0.002-0.076-0.011c-0.043-0.015-0.071-0.032-0.105-0.075c-0.049-0.061-0.379-0.581-0.46-0.703
|
||||
l0.341-0.465c0.064-0.089,0.104-0.141,0.141-0.148c0.028-0.007,0.054-0.011,0.072-0.011c0.025,0,0.05-0.014,0.05-0.039
|
||||
c-0.001-0.031-0.031-0.036-0.055-0.036c-0.079,0-0.165,0.005-0.205,0.005s-0.138-0.005-0.219-0.005
|
||||
c-0.029,0-0.062,0.005-0.062,0.036c0.001,0.028,0.026,0.039,0.043,0.039c0.018,0,0.044,0,0.062,0.006
|
||||
c0.019,0.004,0.024,0.015,0.023,0.018c0,0.013-0.006,0.039-0.021,0.064c-0.032,0.053-0.195,0.293-0.269,0.399
|
||||
c-0.078-0.133-0.16-0.266-0.247-0.418c-0.012-0.02-0.021-0.047-0.02-0.052c0-0.002,0.001-0.008,0.016-0.013
|
||||
c0.016-0.004,0.036-0.006,0.047-0.006c0.021,0,0.049-0.01,0.05-0.039c-0.002-0.031-0.033-0.036-0.061-0.036
|
||||
c-0.079,0-0.208,0.005-0.234,0.005c-0.101,0-0.27-0.005-0.319-0.005c-0.025,0.001-0.055,0.001-0.06,0.034
|
||||
c0,0.021,0.017,0.041,0.037,0.041s0.053,0.004,0.083,0.014c0.069,0.022,0.107,0.062,0.16,0.142l0.362,0.564l-0.402,0.544
|
||||
c-0.071,0.099-0.101,0.125-0.157,0.142c-0.029,0.007-0.06,0.009-0.071,0.009c-0.025,0.001-0.045,0.018-0.046,0.041
|
||||
c0,0.023,0.023,0.036,0.048,0.036h0.036c0.035,0,0.137-0.008,0.176-0.008c0.052,0,0.189,0.008,0.203,0.008h0.038
|
||||
c0.026,0,0.058-0.004,0.06-0.036c-0.002-0.025-0.021-0.039-0.043-0.041c-0.015,0-0.031-0.002-0.045-0.002
|
||||
c-0.013,0-0.022-0.008-0.023-0.018c0-0.001,0-0.003,0-0.003c0-0.015,0.01-0.042,0.028-0.07l0.292-0.453
|
||||
c0.092,0.149,0.201,0.332,0.317,0.52c0.004,0.007,0.005,0.012,0.005,0.014c0,0.004-0.002,0.005-0.002,0.005
|
||||
c-0.021,0.005-0.041,0.021-0.041,0.043c0.006,0.036,0.038,0.034,0.09,0.038c0.171,0.005,0.338,0.005,0.389,0.005h0.062
|
||||
c0.024,0,0.055-0.008,0.058-0.038C30.624,41.788,30.603,41.773,30.581,41.773z M32.359,41.773c-0.019,0-0.05-0.002-0.076-0.011
|
||||
c-0.043-0.015-0.072-0.032-0.106-0.075c-0.049-0.061-0.379-0.581-0.459-0.703l0.341-0.465c0.063-0.089,0.104-0.141,0.141-0.148
|
||||
c0.028-0.007,0.053-0.011,0.072-0.011c0.024,0,0.049-0.014,0.05-0.039c-0.002-0.031-0.032-0.036-0.055-0.036
|
||||
c-0.079,0-0.166,0.005-0.205,0.005c-0.04,0-0.139-0.005-0.22-0.005c-0.028,0-0.061,0.005-0.062,0.036
|
||||
c0.001,0.028,0.025,0.039,0.043,0.039c0.016,0,0.044,0,0.061,0.006c0.02,0.004,0.025,0.015,0.024,0.018
|
||||
c0,0.013-0.006,0.039-0.021,0.064c-0.032,0.053-0.195,0.293-0.269,0.399c-0.079-0.132-0.161-0.266-0.248-0.418
|
||||
c-0.011-0.02-0.02-0.047-0.02-0.052c0-0.002,0.002-0.009,0.016-0.013c0.015-0.004,0.036-0.006,0.046-0.006
|
||||
c0.021,0,0.049-0.01,0.05-0.039c-0.002-0.031-0.032-0.036-0.06-0.036c-0.079,0-0.208,0.005-0.236,0.005
|
||||
c-0.099,0-0.268-0.005-0.317-0.005c-0.026,0.001-0.057,0.001-0.061,0.034c0,0.021,0.018,0.041,0.038,0.041
|
||||
c0.02,0,0.052,0.004,0.083,0.014c0.069,0.022,0.107,0.062,0.159,0.142l0.363,0.564l-0.402,0.544
|
||||
c-0.072,0.099-0.101,0.125-0.157,0.142c-0.029,0.007-0.059,0.009-0.072,0.009c-0.023,0.001-0.045,0.018-0.045,0.041
|
||||
s0.024,0.036,0.047,0.036h0.036c0.035,0,0.138-0.008,0.177-0.008c0.052,0,0.188,0.008,0.201,0.008h0.038
|
||||
c0.027,0,0.059-0.004,0.061-0.036c-0.002-0.025-0.021-0.039-0.043-0.041c-0.017,0-0.032-0.002-0.045-0.002
|
||||
c-0.014,0-0.023-0.008-0.024-0.018v-0.003c0-0.015,0.01-0.042,0.028-0.07l0.293-0.453c0.092,0.149,0.201,0.332,0.315,0.52
|
||||
c0.004,0.007,0.006,0.012,0.006,0.014c0,0.004-0.003,0.005-0.003,0.005c-0.021,0.005-0.041,0.021-0.041,0.043
|
||||
c0.007,0.036,0.038,0.033,0.091,0.038c0.172,0.005,0.339,0.005,0.389,0.005h0.062c0.025,0,0.056-0.008,0.059-0.038
|
||||
C32.401,41.788,32.38,41.773,32.359,41.773z M33.453,41.773c-0.03,0-0.078-0.002-0.104-0.007c-0.057-0.012-0.061-0.026-0.069-0.08
|
||||
c-0.009-0.084-0.009-0.246-0.009-0.44v-0.356c0-0.309,0-0.364,0.004-0.429c0.007-0.069,0.015-0.083,0.063-0.094
|
||||
c0.024-0.005,0.039-0.007,0.06-0.007s0.051-0.012,0.051-0.041c-0.005-0.033-0.035-0.033-0.061-0.034
|
||||
c-0.081,0-0.218,0.005-0.27,0.005c-0.059,0-0.202-0.005-0.281-0.005c-0.031,0.001-0.062,0-0.066,0.034
|
||||
c0,0.029,0.028,0.041,0.051,0.041c0.025,0,0.05,0.002,0.071,0.009c0.04,0.015,0.053,0.025,0.06,0.092
|
||||
c0.002,0.063,0.002,0.12,0.002,0.429v0.356c0,0.194,0,0.356-0.01,0.438c-0.009,0.06-0.015,0.073-0.049,0.083
|
||||
c-0.018,0.004-0.04,0.006-0.07,0.006c-0.031,0-0.051,0.02-0.051,0.039c0.001,0.029,0.031,0.038,0.058,0.038
|
||||
c0.084,0,0.228-0.008,0.273-0.008c0.056,0,0.2,0.008,0.338,0.008c0.025,0,0.055-0.008,0.058-0.038
|
||||
C33.504,41.791,33.482,41.773,33.453,41.773z M34.724,41.773c-0.03,0-0.078-0.002-0.104-0.007c-0.056-0.012-0.06-0.026-0.069-0.08
|
||||
c-0.01-0.084-0.01-0.246-0.01-0.44v-0.356c0-0.309,0-0.364,0.005-0.429c0.008-0.069,0.015-0.083,0.064-0.094
|
||||
c0.023-0.005,0.039-0.007,0.059-0.007c0.021,0,0.051-0.012,0.051-0.041c-0.006-0.033-0.034-0.033-0.061-0.034
|
||||
c-0.082,0-0.218,0.005-0.27,0.005c-0.06,0-0.202-0.005-0.281-0.005c-0.032,0.001-0.062,0-0.067,0.034
|
||||
c0,0.029,0.029,0.041,0.05,0.041c0.025,0,0.051,0.002,0.071,0.009c0.04,0.015,0.052,0.025,0.06,0.092
|
||||
c0.003,0.063,0.003,0.12,0.003,0.429v0.356c0,0.194,0,0.356-0.011,0.438c-0.009,0.06-0.015,0.073-0.049,0.083
|
||||
c-0.018,0.004-0.04,0.006-0.069,0.006c-0.031,0-0.051,0.02-0.051,0.039c0.001,0.029,0.03,0.038,0.058,0.038
|
||||
c0.084,0,0.227-0.008,0.273-0.008c0.057,0,0.2,0.008,0.338,0.008c0.025,0,0.056-0.008,0.059-0.038
|
||||
C34.774,41.791,34.753,41.773,34.724,41.773z M35.995,41.773c-0.03,0-0.077-0.002-0.104-0.007c-0.057-0.012-0.061-0.026-0.069-0.08
|
||||
c-0.01-0.084-0.01-0.246-0.01-0.44v-0.356c0-0.309,0-0.364,0.005-0.429c0.007-0.069,0.014-0.083,0.063-0.094
|
||||
c0.024-0.005,0.039-0.007,0.06-0.007s0.05-0.012,0.05-0.041c-0.005-0.033-0.035-0.033-0.06-0.034c-0.081,0-0.218,0.005-0.27,0.005
|
||||
c-0.059,0-0.202-0.005-0.281-0.005c-0.031,0.001-0.062,0-0.066,0.034c0,0.029,0.028,0.041,0.05,0.041
|
||||
c0.025,0,0.05,0.002,0.071,0.009c0.041,0.015,0.053,0.025,0.06,0.092c0.002,0.063,0.002,0.12,0.002,0.429v0.356
|
||||
c0,0.194,0,0.356-0.009,0.438c-0.009,0.06-0.015,0.073-0.049,0.083c-0.019,0.004-0.04,0.006-0.07,0.006
|
||||
c-0.031,0-0.05,0.02-0.05,0.039c0.001,0.029,0.031,0.038,0.058,0.038c0.084,0,0.228-0.008,0.274-0.008
|
||||
c0.056,0,0.2,0.008,0.338,0.008c0.024,0,0.055-0.008,0.057-0.038C36.045,41.79,36.024,41.773,35.995,41.773z M23.406,31.974v-1.188
|
||||
c0-0.222-0.18-0.402-0.402-0.402c-0.222,0-0.402,0.182-0.402,0.402v1.188H23.406z M25.583,31.974v-1.188
|
||||
c0-0.222-0.18-0.402-0.401-0.402c-0.224,0-0.403,0.182-0.403,0.402v1.188H25.583z M24.092,8.076c-0.232,0-0.422,0.189-0.422,0.422
|
||||
c0,0.233,0.189,0.422,0.422,0.422c0.233,0,0.423-0.188,0.423-0.422C24.514,8.265,24.325,8.076,24.092,8.076z M23.879,19.853h0.426
|
||||
v-0.915h-0.426V19.853L23.879,19.853z M19.84,42.941c-0.232,0-0.422,0.188-0.422,0.422s0.189,0.422,0.422,0.422
|
||||
c0.233,0,0.423-0.188,0.423-0.422S20.073,42.941,19.84,42.941z M19.584,19.869v-1.345c-0.003-0.315-0.158-0.533-0.39-0.635
|
||||
c-0.231,0.102-0.386,0.319-0.389,0.636v1.344H19.584z M19.584,15.548V14.68c-0.003-0.315-0.158-0.534-0.39-0.637
|
||||
c-0.231,0.104-0.386,0.321-0.389,0.637v0.867H19.584z M19.402,24.539v-1.127c0-0.116-0.093-0.209-0.209-0.209
|
||||
s-0.209,0.093-0.209,0.209v1.127H19.402z M19.402,27.931v-1.126c0-0.116-0.093-0.211-0.209-0.211s-0.209,0.095-0.209,0.211v1.126
|
||||
H19.402z M19.525,31.721v-1c0-0.188-0.151-0.339-0.338-0.339c-0.188,0-0.338,0.151-0.338,0.339v1H19.525z M19.194,3.409
|
||||
c-0.233,0-0.422,0.189-0.422,0.422c0,0.233,0.188,0.422,0.422,0.422c0.232,0,0.422-0.188,0.422-0.422
|
||||
C19.616,3.598,19.427,3.409,19.194,3.409z M29.375,19.869v-1.345c-0.002-0.315-0.158-0.533-0.389-0.635
|
||||
c-0.231,0.102-0.387,0.319-0.389,0.636v1.344H29.375z M29.375,15.548V14.68c-0.002-0.315-0.158-0.534-0.389-0.637
|
||||
c-0.231,0.104-0.387,0.321-0.389,0.637v0.867H29.375z M29.194,24.539v-1.127c0-0.116-0.094-0.209-0.209-0.209
|
||||
s-0.209,0.093-0.209,0.209v1.127H29.194z M29.194,27.931v-1.126c0-0.116-0.094-0.211-0.209-0.211s-0.209,0.095-0.209,0.211v1.126
|
||||
H29.194z M29.317,31.721v-1c0-0.188-0.151-0.339-0.338-0.339c-0.188,0-0.338,0.151-0.338,0.339v1H29.317z M28.985,3.409
|
||||
c-0.233,0-0.422,0.189-0.422,0.422c0,0.233,0.188,0.422,0.422,0.422c0.232,0,0.422-0.188,0.422-0.422
|
||||
C29.408,3.598,29.219,3.409,28.985,3.409z M60.109,21.36c-3.838,0-4.703-2.09-4.703-4.415V8.998h2.342v7.803
|
||||
c0,1.532,0.505,2.613,2.523,2.613c1.802,0,2.559-0.757,2.559-2.829V8.998h2.325v7.442C65.155,19.774,63.316,21.36,60.109,21.36z
|
||||
M73.155,21.162v-5.729c0-0.938-0.253-1.496-1.1-1.496c-1.172,0-2.091,1.334-2.091,2.901v4.325h-2.307v-8.956h2.181
|
||||
c0,0.415-0.035,1.117-0.126,1.586l0.019,0.019c0.541-1.063,1.586-1.803,3.046-1.803c2.018,0,2.666,1.298,2.666,2.865v6.289
|
||||
L73.155,21.162L73.155,21.162z M79.102,11.052c-0.793,0-1.424-0.631-1.424-1.405c0-0.757,0.631-1.389,1.424-1.389
|
||||
s1.44,0.613,1.44,1.389C80.543,10.422,79.895,11.052,79.102,11.052z M77.948,21.162v-8.956h2.307v8.956H77.948z M87.211,21.162
|
||||
h-2.343l-3.314-8.956h2.521l1.424,4.036c0.216,0.613,0.434,1.334,0.596,1.982h0.035c0.145-0.612,0.324-1.298,0.54-1.91l1.441-4.108
|
||||
h2.451L87.211,21.162z M98.797,17.054h-5.551c-0.018,1.676,0.812,2.485,2.469,2.485c0.884,0,1.839-0.198,2.613-0.559l0.216,1.784
|
||||
c-0.955,0.378-2.09,0.576-3.207,0.576c-2.848,0-4.433-1.423-4.433-4.576c0-2.739,1.514-4.758,4.198-4.758
|
||||
c2.611,0,3.767,1.784,3.767,4C98.869,16.314,98.851,16.675,98.797,17.054z M95.03,13.702c-0.955,0-1.622,0.703-1.748,1.784h3.298
|
||||
C96.616,14.368,96.004,13.702,95.03,13.702z M105.59,14.278c-1.657-0.343-2.485,0.739-2.485,3.226v3.658h-2.308v-8.956h2.181
|
||||
c0,0.45-0.055,1.171-0.162,1.802h0.036c0.433-1.135,1.298-2.126,2.848-2L105.59,14.278z M108.743,21.342
|
||||
c-0.647,0-1.298-0.071-1.874-0.161l0.055-1.893c0.559,0.145,1.242,0.271,1.929,0.271c0.883,0,1.459-0.361,1.459-0.956
|
||||
c0-1.585-3.621-0.686-3.621-3.73c0-1.567,1.278-2.865,3.802-2.865c0.522,0,1.1,0.072,1.64,0.162l-0.07,1.82
|
||||
c-0.505-0.146-1.101-0.234-1.658-0.234c-0.901,0-1.333,0.36-1.333,0.919c0,1.46,3.676,0.812,3.676,3.713
|
||||
C112.744,20.153,111.194,21.342,108.743,21.342z M115.861,11.052c-0.793,0-1.424-0.631-1.424-1.405c0-0.757,0.631-1.389,1.424-1.389
|
||||
s1.44,0.613,1.44,1.389C117.302,10.422,116.654,11.052,115.861,11.052z M114.708,21.162v-8.956h2.308v8.956H114.708z
|
||||
M122.851,21.342c-1.982,0-2.613-0.721-2.613-2.811V13.99h-1.531v-1.784h1.531V9.449l2.307-0.613v3.37h2.182v1.784h-2.182v3.928
|
||||
c0,1.153,0.271,1.479,1.063,1.479c0.378,0,0.793-0.055,1.117-0.145v1.856C124.147,21.252,123.481,21.342,122.851,21.342z
|
||||
M131.731,21.162c0-0.521,0.019-1.045,0.091-1.514l-0.019-0.019c-0.434,1.01-1.531,1.712-2.865,1.712c-1.621,0-2.56-0.919-2.56-2.36
|
||||
c0-2.145,2.126-3.279,5.172-3.279v-0.487c0-0.937-0.45-1.423-1.747-1.423c-0.812,0-1.894,0.271-2.649,0.703l-0.198-1.928
|
||||
c0.901-0.324,2.056-0.56,3.208-0.56c2.884,0,3.694,1.172,3.694,3.118v3.73c0,0.721,0.018,1.567,0.054,2.307L131.731,21.162
|
||||
L131.731,21.162z M128.561,10.729c-0.687,0-1.243-0.56-1.243-1.244c0-0.703,0.558-1.244,1.243-1.244
|
||||
c0.685,0,1.243,0.541,1.243,1.244C129.804,10.169,129.245,10.729,128.561,10.729z M131.552,17.198c-2.433,0-2.974,0.703-2.974,1.423
|
||||
c0,0.577,0.396,0.955,1.063,0.955c1.135,0,1.909-1.081,1.909-2.162L131.552,17.198L131.552,17.198z M132.2,10.729
|
||||
c-0.685,0-1.243-0.56-1.243-1.244c0-0.703,0.56-1.244,1.243-1.244s1.244,0.541,1.244,1.244
|
||||
C133.444,10.169,132.885,10.729,132.2,10.729z M139.55,21.342c-1.981,0-2.612-0.721-2.612-2.811V13.99h-1.531v-1.784h1.531V9.449
|
||||
l2.307-0.613v3.37h2.181v1.784h-2.181v3.928c0,1.153,0.271,1.479,1.063,1.479c0.379,0,0.793-0.055,1.116-0.145v1.856
|
||||
C140.847,21.252,140.181,21.342,139.55,21.342z M54.955,40.202V38.4l4.379-7.137c0.252-0.414,0.504-0.774,0.793-1.153
|
||||
c-0.433,0.019-1.009,0.036-2.217,0.036h-2.811v-2.108h7.856v1.874l-4.631,7.316c-0.18,0.288-0.342,0.559-0.558,0.848
|
||||
c0.306-0.036,1.135-0.036,2.631-0.036h2.631v2.162H54.955z M70.484,40.202c0-0.414,0.018-1.117,0.107-1.586l-0.018-0.018
|
||||
c-0.541,1.062-1.567,1.802-3.045,1.802c-2.02,0-2.667-1.298-2.667-2.865v-6.289h2.289v5.73c0,0.937,0.252,1.496,1.117,1.496
|
||||
c1.171,0,2.071-1.334,2.071-2.901v-4.325h2.308v8.956H70.484z M66.826,29.769c-0.685,0-1.243-0.56-1.243-1.244
|
||||
c0-0.702,0.56-1.243,1.243-1.243c0.685,0,1.244,0.541,1.244,1.243C68.069,29.21,67.511,29.769,66.826,29.769z M70.466,29.769
|
||||
c-0.685,0-1.243-0.56-1.243-1.244c0-0.702,0.559-1.243,1.243-1.243c0.686,0,1.243,0.541,1.243,1.243
|
||||
C71.709,29.21,71.151,29.769,70.466,29.769z M79.926,33.318c-1.656-0.343-2.485,0.739-2.485,3.226v3.658h-2.308v-8.956h2.182
|
||||
c0,0.45-0.055,1.171-0.162,1.802h0.036c0.432-1.135,1.297-2.126,2.847-2L79.926,33.318z M82.854,30.093
|
||||
c-0.793,0-1.424-0.631-1.424-1.405c0-0.756,0.631-1.388,1.424-1.388s1.44,0.612,1.44,1.388
|
||||
C84.295,29.462,83.646,30.093,82.854,30.093z M81.7,40.202v-8.956h2.307v8.956H81.7z M89.729,40.364
|
||||
c-2.486,0-4.036-1.297-4.036-4.343c0-2.793,1.46-4.938,4.632-4.938c0.612,0,1.261,0.09,1.838,0.252l-0.233,2.001
|
||||
c-0.486-0.181-1.046-0.325-1.622-0.325c-1.46,0-2.198,1.081-2.198,2.775c0,1.531,0.595,2.577,2.126,2.577
|
||||
c0.612,0,1.279-0.127,1.767-0.379l0.181,1.965C91.567,40.185,90.684,40.364,89.729,40.364z M99.478,40.202v-5.729
|
||||
c0-0.938-0.252-1.496-1.1-1.496c-1.172,0-2.091,1.334-2.091,2.9v4.325h-2.307V27.047h2.307v3.839c0,0.541-0.036,1.298-0.162,1.82
|
||||
l0.036,0.019c0.522-1.01,1.55-1.677,2.938-1.677c2.018,0,2.667,1.299,2.667,2.865v6.289H99.478z M105.334,31.716
|
||||
c-1.461,0-1.813-0.788-1.813-1.706v-2.972h1.061v2.91c0,0.537,0.17,0.877,0.822,0.877c0.599,0,0.83-0.252,0.83-0.938v-2.85h1.047
|
||||
v2.788C107.279,31.118,106.552,31.716,105.334,31.716z M108.163,31.628v-0.83l1.477-2.468c0.073-0.122,0.155-0.238,0.243-0.354
|
||||
c-0.12,0.007-0.277,0.014-0.721,0.014h-0.951v-0.952h2.972v0.863l-1.558,2.502c-0.055,0.089-0.107,0.164-0.177,0.259
|
||||
c0.089-0.014,0.347-0.014,0.891-0.014h0.87v0.979L108.163,31.628L108.163,31.628z M114.922,31.628v-1.911h-1.68v1.911h-1.061v-4.59
|
||||
h1.061V28.8h1.68v-1.762h1.062v4.59H114.922z M0,24.091C0,10.786,10.786,0,24.091,0l0,0c13.306,0,24.092,10.786,24.092,24.091l0,0
|
||||
c0,13.306-10.786,24.092-24.092,24.092l0,0C10.786,48.182,0,37.396,0,24.091L0,24.091z M7.477,7.477
|
||||
C3.225,11.73,0.595,17.602,0.595,24.091l0,0c0,6.489,2.629,12.361,6.882,16.614l0,0c4.253,4.252,10.125,6.881,16.613,6.881l0,0
|
||||
c6.489,0,12.361-2.629,16.614-6.881l0,0c4.252-4.253,6.882-10.125,6.882-16.614l0,0c0-6.488-2.629-12.36-6.882-16.613l0,0
|
||||
C36.452,3.224,30.58,0.594,24.091,0.594l0,0C17.602,0.594,11.73,3.224,7.477,7.477L7.477,7.477z M24.456,39.232h0.194v-0.34h-0.462
|
||||
v0.753h0.269L24.456,39.232z M24.091,1.568c-12.436,0-22.516,10.081-22.516,22.516c0,12.437,10.081,22.518,22.516,22.518
|
||||
c12.436,0,22.516-10.081,22.517-22.518C46.606,11.649,36.526,1.568,24.091,1.568z M21.598,43.19h-1.021v0.34h1.021v2.53
|
||||
c-5.112-0.573-9.696-2.889-13.146-6.338c-0.495-0.495-0.967-1.015-1.414-1.555c0.091-0.549,0.565-0.967,1.141-0.967
|
||||
c0.639,0,1.155,0.518,1.155,1.156v1.11h1.468v-0.007l0,0v-1.104c0-0.64,0.519-1.156,1.156-1.156c0.639,0,1.157,0.518,1.157,1.156
|
||||
v1.11h1.468v-0.007l0,0v-1.104c0-0.64,0.519-1.156,1.156-1.156s1.157,0.518,1.157,1.156v1.11h1.468v-0.007l0,0v-1.104
|
||||
c0-0.64,0.518-1.156,1.157-1.156c0.639,0,1.155,0.518,1.155,1.156v1.11h0.922V43.19z M25.794,46.135
|
||||
c-0.562,0.044-1.13,0.065-1.703,0.065c-0.571,0-1.14-0.021-1.7-0.063l-0.418-2.606h0.481c-0.12-0.286-0.329-0.786-0.409-0.989
|
||||
c-0.13-0.326-0.032-0.542,0.185-0.626c0.211-0.082,0.449,0.034,0.523,0.312l0.211,0.792h0.309v0.511l0,0v0.102h-0.729v0.341h1.066
|
||||
l0.001-0.441h1.293l-0.001-0.511h0.31l0.213-0.792c0.075-0.278,0.315-0.396,0.528-0.312c0.218,0.084,0.315,0.3,0.186,0.626
|
||||
c-0.035,0.089-0.093,0.235-0.154,0.396l-0.288,0.695H24.92v0.341h1.005l0.184-0.441h0.104L25.794,46.135z M23.391,38.684h1.4
|
||||
c0.055,0.129,0.188,0.454,0.271,0.643c0.057,0.131,0.13,0.271,0.041,0.416c-0.106,0.171-0.245,0.146-0.342,0.139
|
||||
c-0.006-0.001-0.012,0-0.018-0.001c-0.098,0.269-0.352,0.459-0.653,0.459c-0.301,0-0.555-0.19-0.652-0.459
|
||||
c-0.005,0.001-0.013,0-0.018,0.001c-0.098,0.009-0.235,0.032-0.342-0.139c-0.089-0.145-0.016-0.285,0.041-0.416
|
||||
C23.204,39.138,23.337,38.812,23.391,38.684z M23.18,37.23l0.447,0.641l0.458-0.869l0.458,0.869l0.448-0.641l-0.178,1.111h-1.456
|
||||
L23.18,37.23z M23.715,40.376c0.105,0.293,0.385,0.504,0.715,0.504c0.356,0,0.654-0.247,0.736-0.58
|
||||
c0.222,0.012,0.442,0.075,0.661,0.261c0.211,0.177,0.293,0.48,0.323,0.632h-4.116c0.029-0.15,0.112-0.455,0.323-0.632
|
||||
C22.808,40.181,23.268,40.305,23.715,40.376z M25.414,41.532c-0.155,0.586-0.688,1.019-1.322,1.019
|
||||
c-0.634,0-1.167-0.433-1.321-1.019H25.414z M39.729,39.723c-3.45,3.449-8.033,5.764-13.145,6.337v-2.53h0.935
|
||||
c1.731,0,2.983-0.146,2.983-0.146s-1.289-0.194-2.983-0.194h-0.935v-3.721h1.117v-0.007l0,0v-1.104c0-0.64,0.519-1.156,1.157-1.156
|
||||
s1.157,0.519,1.157,1.156v1.11h1.467v-0.007l0,0v-1.104c0-0.64,0.518-1.156,1.156-1.156c0.638,0,1.155,0.519,1.155,1.156v1.11h1.469
|
||||
v-0.007l0,0v-1.104c0-0.64,0.518-1.156,1.156-1.156c0.64,0,1.156,0.519,1.156,1.156v1.11h1.468v-0.007l0,0v-1.104
|
||||
c0-0.64,0.519-1.156,1.157-1.156c0.506,0,0.937,0.326,1.092,0.78C40.803,38.589,40.281,39.171,39.729,39.723z M41.541,37.673
|
||||
c-0.257-0.541-0.808-0.916-1.445-0.916c-0.885,0-1.602,0.718-1.602,1.603v0.517h-0.579v-0.517c0-0.885-0.717-1.603-1.601-1.603
|
||||
s-1.602,0.718-1.602,1.603v0.517h-0.58v-0.517c0-0.885-0.717-1.603-1.601-1.603s-1.601,0.718-1.601,1.603v0.517h-0.58v-0.517
|
||||
c0-0.885-0.718-1.603-1.602-1.603s-1.601,0.718-1.601,1.603v0.517h-0.598c-0.182-1.198-1.213-2.118-2.462-2.118
|
||||
s-2.279,0.92-2.462,2.118h-0.614v-0.517c0-0.885-0.718-1.603-1.602-1.603s-1.601,0.718-1.601,1.603v0.517h-0.58v-0.517
|
||||
c0-0.885-0.716-1.603-1.602-1.603c-0.884,0-1.601,0.718-1.601,1.603v0.517h-0.58v-0.517c0-0.885-0.717-1.603-1.601-1.603
|
||||
c-0.885,0-1.602,0.718-1.602,1.603v0.517H9.672v-0.517c0-0.885-0.717-1.603-1.601-1.603c-0.634,0-1.181,0.369-1.44,0.903
|
||||
c-0.39-0.501-0.758-1.02-1.104-1.554h37.129C42.306,36.645,41.935,37.168,41.541,37.673z M43.027,35.515H5.155
|
||||
c-0.11-0.183-0.217-0.366-0.322-0.553h38.517C43.243,35.148,43.137,35.333,43.027,35.515z M20.275,7.089v0.187
|
||||
C20.27,7.313,20.26,7.368,20.241,7.42c-0.032,0.085-0.111,0.221-0.217,0.386c-0.04,0.064-0.11,0.174-0.188,0.33
|
||||
C19.735,7.982,19.65,7.88,19.59,7.817c-0.066-0.07-0.154-0.16-0.228-0.246c-0.157-0.187-0.166-0.298-0.166-0.298l0,0
|
||||
c0,0-0.005,0.11-0.163,0.297c-0.073,0.085-0.161,0.175-0.228,0.246c-0.062,0.063-0.147,0.167-0.25,0.325
|
||||
c-0.08-0.161-0.151-0.272-0.192-0.341c-0.105-0.165-0.185-0.3-0.217-0.385c-0.03-0.081-0.041-0.174-0.042-0.184V7.089H20.275z
|
||||
M18.109,6.749c0.017-0.277,0.092-0.805,0.362-1.164c0.204-0.278,0.407-0.443,0.557-0.54C19.096,5,19.151,4.973,19.19,4.954
|
||||
c0.04,0.02,0.095,0.046,0.164,0.091c0.147,0.097,0.352,0.262,0.557,0.54c0.271,0.359,0.345,0.887,0.362,1.164H18.109z M20.354,7.988
|
||||
c0.062,0.085,0.125,0.209,0.194,0.359c0.096,0.233,0.121,0.462,0.124,0.63v2.492h-0.642c0-0.548,0-1.756,0-2.491
|
||||
c0.002-0.171,0.033-0.409,0.124-0.632C20.234,8.17,20.296,8.07,20.354,7.988z M19.682,8.994l-0.001,2.476h-0.974
|
||||
c0-0.689-0.001-2.477-0.001-2.478c0.008-0.162,0.021-0.386,0.138-0.617c0.122-0.242,0.354-0.386,0.354-0.386h0.001
|
||||
c0,0,0.219,0.144,0.342,0.386C19.664,8.618,19.675,8.828,19.682,8.994z M18.245,8.343c0.091,0.224,0.106,0.465,0.109,0.636
|
||||
l0.003,2.491h-0.634V8.975c0.003-0.168,0.021-0.396,0.116-0.631c0.07-0.149,0.138-0.273,0.195-0.358
|
||||
C18.106,8.091,18.175,8.193,18.245,8.343z M21.164,11.877v0.807h-3.941v-0.807H21.164z M21.164,13.022v3.141H20.21V14.75
|
||||
c0.001-0.469-0.173-0.866-0.459-1.138c-0.153-0.147-0.338-0.258-0.542-0.328c-0.013-0.004-0.012-0.005-0.012-0.005
|
||||
s-0.002,0-0.015,0.005c-0.204,0.07-0.39,0.181-0.543,0.328c-0.286,0.271-0.459,0.669-0.458,1.138v1.414h-0.958v-3.141L21.164,13.022
|
||||
L21.164,13.022z M19.87,14.75v1.414h-1.35V14.75c0.005-0.546,0.272-0.925,0.675-1.103C19.596,13.824,19.865,14.203,19.87,14.75z
|
||||
M21.164,16.503v3.995h-0.876v-1.995c0-0.468-0.176-0.875-0.47-1.155c-0.157-0.15-0.344-0.263-0.55-0.333
|
||||
c-0.011-0.005-0.074-0.024-0.074-0.024s-0.062,0.02-0.067,0.022c-0.207,0.07-0.396,0.185-0.555,0.335
|
||||
c-0.294,0.279-0.471,0.688-0.47,1.154v1.995h-0.88v-3.995L21.164,16.503L21.164,16.503z M19.948,18.506v1.992h-1.507v-1.995
|
||||
c0-0.384,0.141-0.694,0.364-0.909c0.11-0.105,0.241-0.185,0.389-0.241h0.001c0.147,0.058,0.276,0.136,0.387,0.24
|
||||
c0.226,0.215,0.364,0.525,0.365,0.91L19.948,18.506z M21.164,20.838v4.755h-3.941v-4.755H21.164z M21.164,25.933v3.103h-3.941
|
||||
v-3.103H21.164z M21.164,29.375v5.247h-3.941v-5.247H21.164z M24.634,16.538v1.416l-0.542-0.542l-0.541,0.541v-1.415H24.634z
|
||||
M23.566,16.13l0.396-1.694l0.163-2.835l0.098,2.833l0.396,1.696H23.566z M24.092,17.988l2.511,2.51h-5.021L24.092,17.988z
|
||||
M26.603,20.838v1.921h-5.037v-1.921H26.603z M26.603,23.354v2.238h-1.575v-0.561c0-0.516-0.419-0.938-0.938-0.938
|
||||
c-0.517,0-0.937,0.421-0.937,0.938v0.561h-1.589v-2.238H26.603z M23.155,25.933v1.998h1.873v-1.998h1.575v3.103h-5.037v-3.103
|
||||
H23.155z M26.603,29.375v5.247h-5.037v-5.247H26.603z M30.067,7.089v0.187c-0.005,0.038-0.017,0.093-0.035,0.145
|
||||
C30,7.505,29.921,7.64,29.815,7.805c-0.04,0.064-0.11,0.174-0.188,0.33c-0.101-0.153-0.186-0.256-0.246-0.318
|
||||
c-0.065-0.07-0.154-0.16-0.228-0.246c-0.158-0.187-0.166-0.298-0.166-0.298l0,0c0,0-0.005,0.11-0.163,0.297
|
||||
c-0.073,0.085-0.161,0.175-0.228,0.246c-0.062,0.063-0.147,0.167-0.25,0.325c-0.08-0.161-0.151-0.272-0.192-0.341
|
||||
c-0.105-0.165-0.185-0.3-0.217-0.385c-0.031-0.082-0.041-0.177-0.042-0.185V7.089H30.067z M27.901,6.749
|
||||
c0.017-0.277,0.092-0.805,0.362-1.164c0.204-0.278,0.407-0.443,0.557-0.54C28.888,5,28.942,4.973,28.982,4.954
|
||||
c0.04,0.02,0.095,0.046,0.163,0.091c0.147,0.097,0.352,0.262,0.557,0.54c0.271,0.359,0.345,0.887,0.362,1.164H27.901z M30.146,7.988
|
||||
c0.061,0.085,0.125,0.209,0.193,0.359c0.096,0.233,0.121,0.462,0.124,0.63v2.492h-0.642c0-0.548,0-1.756,0-2.491
|
||||
c0.003-0.171,0.033-0.409,0.125-0.632C30.025,8.17,30.088,8.07,30.146,7.988z M29.473,8.994l-0.001,2.476H28.5
|
||||
c0-0.689-0.001-2.477-0.001-2.478c0.008-0.162,0.021-0.386,0.138-0.617c0.122-0.242,0.354-0.386,0.354-0.386h0.001
|
||||
c0,0,0.219,0.144,0.342,0.386C29.455,8.618,29.466,8.828,29.473,8.994z M28.036,8.343c0.091,0.224,0.106,0.465,0.109,0.636
|
||||
l0.003,2.491h-0.635V8.975c0.004-0.168,0.021-0.396,0.116-0.631c0.07-0.149,0.138-0.273,0.194-0.358
|
||||
C27.898,8.091,27.966,8.193,28.036,8.343z M30.956,11.877v0.807h-3.941v-0.807H30.956z M30.956,13.022v3.141h-0.954V14.75
|
||||
c0-0.469-0.173-0.866-0.459-1.138c-0.153-0.147-0.339-0.258-0.543-0.328c-0.013-0.004-0.012-0.005-0.012-0.005s-0.002,0-0.015,0.005
|
||||
c-0.204,0.07-0.39,0.181-0.543,0.328c-0.286,0.271-0.459,0.669-0.459,1.138v1.414h-0.958v-3.141L30.956,13.022L30.956,13.022z
|
||||
M29.662,14.75v1.414h-1.351V14.75c0.005-0.546,0.272-0.925,0.675-1.103C29.388,13.824,29.657,14.203,29.662,14.75z M30.956,16.503
|
||||
v3.995H30.08v-1.995c0-0.468-0.176-0.875-0.47-1.155c-0.157-0.15-0.344-0.263-0.55-0.333c-0.01-0.005-0.074-0.024-0.074-0.024
|
||||
s-0.062,0.02-0.068,0.022c-0.207,0.07-0.396,0.185-0.555,0.335c-0.294,0.279-0.47,0.688-0.47,1.154v1.995h-0.88v-3.995
|
||||
L30.956,16.503L30.956,16.503z M29.74,18.506v1.992h-1.506v-1.995c0-0.384,0.141-0.694,0.364-0.909
|
||||
c0.11-0.105,0.241-0.185,0.388-0.241h0.001c0.147,0.058,0.277,0.136,0.388,0.24c0.225,0.215,0.364,0.525,0.365,0.91V18.506z
|
||||
M30.956,20.838v4.755h-3.941v-4.755H30.956z M30.956,25.933v3.103h-3.941v-3.103H30.956z M30.956,29.375v5.247h-3.941v-5.247
|
||||
H30.956z M31.562,34.622V11.469h-0.497V8.917c-0.005-0.317-0.051-0.519-0.191-0.844c-0.036-0.082-0.087-0.172-0.145-0.262V6.889
|
||||
c0,0-0.001-0.725-0.3-1.27c-0.325-0.595-0.956-0.92-1.264-1.049c-0.07-0.033-0.117-0.048-0.126-0.05l-0.051-0.019l-0.063,0.019
|
||||
c-0.03,0.009-0.529,0.167-1.004,0.812c-0.43,0.593-0.444,1.354-0.448,1.563c0,0.034,0.001,0.054,0.001,0.057v0.851
|
||||
c-0.058,0.084-0.138,0.211-0.226,0.398c-0.132,0.324-0.141,0.624-0.146,0.804v2.465h-0.5v8.452l-1.368-1.367v-2.413l-0.464-1.558
|
||||
l-0.68-5.341l0,0l-0.501,5.117l-0.466,1.782v2.234l-1.354,1.355v-8.265h-0.497V8.917c-0.005-0.317-0.051-0.519-0.192-0.844
|
||||
c-0.035-0.082-0.087-0.172-0.144-0.262V6.889c0,0-0.001-0.725-0.3-1.27c-0.326-0.595-0.957-0.92-1.264-1.049
|
||||
c-0.07-0.033-0.117-0.048-0.126-0.05l-0.051-0.019l-0.063,0.019c-0.031,0.009-0.529,0.167-1.005,0.812
|
||||
c-0.43,0.593-0.444,1.354-0.448,1.563c0,0.034,0,0.054,0,0.057v0.851c-0.058,0.084-0.138,0.211-0.226,0.398
|
||||
c-0.131,0.324-0.141,0.624-0.145,0.804v2.465h-0.5v23.151H4.646c-1.702-3.133-2.672-6.721-2.672-10.538
|
||||
c0-6.106,2.475-11.635,6.477-15.639c4.003-4.002,9.531-6.479,15.639-6.479c6.108,0,11.637,2.476,15.64,6.479
|
||||
c4.002,4.003,6.478,9.531,6.478,15.639c0,3.817-0.97,7.405-2.672,10.538L31.562,34.622L31.562,34.622z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 53 KiB |
@ -646,8 +646,8 @@ function handleAssessmentResponse(depict_url, data) {
|
||||
var reactivityCentersImgSrc = null;
|
||||
|
||||
if (data['assessment']['node'] !== undefined) {
|
||||
functionalGroupsImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "'>";
|
||||
reactivityCentersImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "'>"
|
||||
functionalGroupsImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "&highlight=true'>";
|
||||
reactivityCentersImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "&highlightReactivity=true'>"
|
||||
} else {
|
||||
functionalGroupsImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">";
|
||||
reactivityCentersImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">"
|
||||
@ -751,7 +751,7 @@ function handleAssessmentResponse(depict_url, data) {
|
||||
var predProb = "<a class='list-group-item'>Predicted probability: " + transObj['probability'].toFixed(2) + "</a>";
|
||||
var timesTriggered = "<a class='list-group-item'>This rule has triggered " + transObj['times_triggered'] + " times in the training set</a>";
|
||||
var reliability = "<a class='list-group-item'>Reliability: " + transObj['reliability'].toFixed(2) + " (" + (transObj['reliability'] > data['ad_params']['reliability_threshold'] ? ">" : "<") + " Reliability Threshold of " + data['ad_params']['reliability_threshold'] + ") </a>";
|
||||
var localCompatibility = "<a class='list-group-item'>Local Compatibility: " + transObj['local_compatibility'].toFixed(2) + " (" + (transObj['local_compatibility'] > data['ad_params']['local_compatibilty_threshold'] ? ">" : "<") + " Local Compatibility Threshold of " + data['ad_params']['local_compatibilty_threshold'] + ")</a>";
|
||||
var localCompatibility = "<a class='list-group-item'>Local Compatibility: " + transObj['local_compatibility'].toFixed(2) + " (" + (transObj['local_compatibility'] > data['ad_params']['local_compatibility_threshold'] ? ">" : "<") + " Local Compatibility Threshold of " + data['ad_params']['local_compatibility_threshold'] + ")</a>";
|
||||
|
||||
var transImg = "<img width='100%' src='" + transObj['rule']['url'] + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "'>";
|
||||
|
||||
|
||||
@ -444,6 +444,13 @@ function serializeSVG(svgElement) {
|
||||
line.setAttribute("fill", style.fill);
|
||||
});
|
||||
|
||||
svgElement.querySelectorAll("line.link_no_arrow").forEach(line => {
|
||||
const style = getComputedStyle(line);
|
||||
line.setAttribute("stroke", style.stroke);
|
||||
line.setAttribute("stroke-width", style.strokeWidth);
|
||||
line.setAttribute("fill", style.fill);
|
||||
});
|
||||
|
||||
const serializer = new XMLSerializer();
|
||||
let svgString = serializer.serializeToString(svgElement);
|
||||
|
||||
@ -455,7 +462,26 @@ function serializeSVG(svgElement) {
|
||||
return svgString;
|
||||
}
|
||||
|
||||
function shrinkSVG(svgSelector) {
|
||||
|
||||
const svg = d3.select(svgSelector);
|
||||
const node = svg.node();
|
||||
|
||||
// Compute bounding box of everything inside the SVG
|
||||
const bbox = node.getBBox();
|
||||
|
||||
const padding = 10;
|
||||
svg.attr("viewBox",
|
||||
`${bbox.x - padding} ${bbox.y - padding} ${bbox.width + 2 * padding} ${bbox.height + 2 * padding}`
|
||||
)
|
||||
.attr("width", bbox.width + 2 * padding)
|
||||
.attr("height", bbox.height + 2 * padding);
|
||||
|
||||
return bbox;
|
||||
}
|
||||
|
||||
function downloadSVG(svgElement, filename = 'chart.svg') {
|
||||
shrinkSVG("#" + svgElement.id);
|
||||
const svgString = serializeSVG(svgElement);
|
||||
const blob = new Blob([svgString], {type: 'image/svg+xml;charset=utf-8'});
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
<a role="button" data-toggle="modal" data-target="#edit_compound_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#add_structure_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Add Structure</a>
|
||||
@ -11,6 +15,10 @@
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
||||
|
||||
@ -3,10 +3,18 @@
|
||||
<a role="button" data-toggle="modal" data-target="#edit_compound_structure_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a>
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
<a role="button" data-toggle="modal" data-target="#edit_node_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Node</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
|
||||
@ -22,6 +22,10 @@
|
||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a>
|
||||
</li>
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a class="button" data-toggle="modal" data-target="#identify_missing_rules_modal">
|
||||
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing Rules</a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li>
|
||||
<a class="button" data-toggle="modal" data-target="#edit_pathway_modal">
|
||||
@ -31,6 +35,10 @@
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
{# <li>#}
|
||||
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
|
||||
{# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#}
|
||||
|
||||
@ -3,10 +3,18 @@
|
||||
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
<a role="button" data-toggle="modal" data-target="#edit_rule_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
|
||||
71
templates/collections/joblog.html
Normal file
71
templates/collections/joblog.html
Normal file
@ -0,0 +1,71 @@
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% load envipytags %}
|
||||
{% block content %}
|
||||
|
||||
<div class="panel-group" id="reviewListAccordion">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||
Jobs
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
Job Logs Desc
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="job-accordion-link" data-toggle="collapse" data-parent="#job-accordion" href="#jobs">
|
||||
Jobs
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="jobs"
|
||||
class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item" id="job-content">
|
||||
<table class="table table-bordered table-hover">
|
||||
<tr style="background-color: rgba(0, 0, 0, 0.08);">
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Queued</th>
|
||||
<th scope="col">Done</th>
|
||||
<th scope="col">Result</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
{% for job in jobs %}
|
||||
<tr>
|
||||
<td>{{ job.task_id }}</td>
|
||||
<td>{{ job.job_name }}</td>
|
||||
<td>{{ job.status }}</td>
|
||||
<td>{{ job.created }}</td>
|
||||
<td>{{ job.done_at }}</td>
|
||||
{% if job.task_result and job.task_result|is_url == True %}
|
||||
<td><a href="{{ job.task_result }}">Result</a></td>
|
||||
{% elif job.task_result %}
|
||||
<td>{{ job.task_result|slice:"40" }}...</td>
|
||||
{% else %}
|
||||
<td>Empty</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Unreviewable objects such as User / Group / Setting -->
|
||||
<ul class='list-group'>
|
||||
{% for obj in objects %}
|
||||
{% if object_type == 'user' %}
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username }}</a>
|
||||
{% else %}
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
55
templates/compare.html
Normal file
55
templates/compare.html
Normal file
@ -0,0 +1,55 @@
|
||||
{% extends "framework.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="text" class="form-control" id="smiles" name="smiles" placeholder="SMILES"
|
||||
value="{{ smiles }}"/>
|
||||
<input type="text" class="form-control" id="smiles" name="smirks" placeholder="SMIRKS"
|
||||
value="{{ smirks }}"/>
|
||||
<button type="submit" class="btn btn-primary">Test</button>
|
||||
</form>
|
||||
</div>
|
||||
{% if result %}
|
||||
{{ smiles }}<p></p>
|
||||
<img width='400' src='{% url 'depict' %}?smiles={{ smiles|urlencode }}'><br>
|
||||
<p></p>
|
||||
{% if rule %}
|
||||
{{ smirks }}
|
||||
<p></p>
|
||||
{{ rule.reactants_smarts }}
|
||||
<p></p>
|
||||
{{ rule.products_smarts }}
|
||||
<p></p>
|
||||
<div>
|
||||
{{ rule.as_svg|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<h2>Diff</h2>
|
||||
{% if diff %}
|
||||
{% for d in diff %}
|
||||
{{ d }}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{ "No diff" }}
|
||||
{% endif %}
|
||||
<div>
|
||||
<div class="col-md-6">
|
||||
<h2>Ambit</h2>
|
||||
{% for p in ambit_res %}
|
||||
{{ p }}<br>
|
||||
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h2>RDKit</h2>
|
||||
{% for p in rdkit_res %}
|
||||
{{ p }}<br>
|
||||
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@ -6,8 +6,7 @@
|
||||
<h4 class="alert-heading">{{ error_message }}</h4>
|
||||
<hr>
|
||||
<p class="mb-0">
|
||||
{{ error_detail }}<br>
|
||||
The error was logged and will be investigated.
|
||||
{{ error_detail }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse">
|
||||
<ul class="nav navbar-nav navbar-nav-framework">
|
||||
<li>
|
||||
<a class="button" data-toggle="modal" data-target="#predict_modal">
|
||||
<a href="#" data-toggle="modal" data-target="#predict_modal">
|
||||
Predict Pathway
|
||||
</a>
|
||||
</li>
|
||||
@ -134,7 +134,7 @@
|
||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a>
|
||||
<ul role="menu" class="dropdown-menu">
|
||||
<!--<li><a href="{{ meta.server_url }}/funding" id="fundingLink">Funding</a></li>-->
|
||||
<li><a href="https://envipath.com/license/" id="licenceLink">Licences</a></li>
|
||||
<li><a href="https://community.envipath.org/t/envipath-license/109" id="licenceLink">Licences</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a target="_blank" href="https://wiki.envipath.org/" id="wikiLink">Documentation Wiki</a>
|
||||
</li>
|
||||
@ -227,19 +227,21 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<ul class="nav nav-pills nav-justified">
|
||||
<li><a href="http://eawag.ch" target="_blank">
|
||||
<img id="image-ealogo"
|
||||
height="60"
|
||||
src='{% static "/images/ealogo.gif" %}'
|
||||
alt="Eawag"/>
|
||||
<li>
|
||||
<a href="http://ml.auckland.ac.nz" target="_blank">
|
||||
<img id="image-uoalogo" height="60" src='{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}'
|
||||
alt="The Univserity of Auckland"/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://ml.auckland.ac.nz" target="_blank">
|
||||
<img id="image-uoalogo"
|
||||
height="60"
|
||||
src='{% static "/images/uoa.png" %}'
|
||||
alt="The Univserity of Auckland"/>
|
||||
<a href="https://eawag.ch" target="_blank">
|
||||
<img id="image-ealogo" height="60" src='{% static "/images/ealogo.gif" %}' alt="Eawag"/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.uzh.ch/" target="_blank">
|
||||
<img id="image-ufzlogo" height="60" src='{% static "/images/uzh-logo.svg" %}'
|
||||
alt="University of Zurich"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -103,6 +103,7 @@
|
||||
var textSmiles = $('#index-form-text-input').val().trim();
|
||||
|
||||
if (textSmiles === '') {
|
||||
$(this).prop("disabled", false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{% extends "framework.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="panel-group" id="migration-detail">
|
||||
<div class="panel-group" id="migration-detail">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||
Migration Status for {{ bt_rule_name }}
|
||||
@ -32,19 +32,14 @@
|
||||
</div>
|
||||
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
||||
<div class="panel-body list-group-item">
|
||||
{% if obj.status %}
|
||||
<p>Products generated by AMBIT: {{ obj.ambit_smiles }}</p>
|
||||
<p>Products generated by RDKit: {{ obj.rdkit_smiles }}</p>
|
||||
{% else %}
|
||||
<pre>{{ obj.detail }}</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
@ -15,12 +15,12 @@
|
||||
enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<label class="btn btn-primary" for="jsonFile">
|
||||
<input id="jsonFile" name="file" type="file" style="display:none;"
|
||||
onchange="$('#upload-file-info').html(this.files[0].name)">
|
||||
<label class="btn btn-primary" for="legacyJsonFile">
|
||||
<input id="legacyJsonFile" name="file" type="file" style="display:none;"
|
||||
onchange="$('#upload-legacy-file-info').html(this.files[0].name)">
|
||||
Choose JSON File
|
||||
</label>
|
||||
<span class="label label-info" id="upload-file-info"></span>
|
||||
<span class="label label-info" id="upload-legacy-file-info"></span>
|
||||
<input type="hidden" value="import-legacy-package-json" name="hidden" readonly="">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
@ -18,13 +18,19 @@
|
||||
prediction. You just need to set a name and the packages
|
||||
you want the object to be based on. There are multiple types of models available.
|
||||
For additional information have a look at our
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki >></a>
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki
|
||||
>></a>
|
||||
</div>
|
||||
<!-- Name -->
|
||||
<label for="model-name">Name</label>
|
||||
<input id="model-name" name="model-name" class="form-control" placeholder="Name"/>
|
||||
|
||||
<!-- Description -->
|
||||
<label for="model-description">Description</label>
|
||||
<input id="model-description" name="model-description" class="form-control"
|
||||
placeholder="Description"/>
|
||||
|
||||
<!-- Model Type -->
|
||||
<label for="model-type">Model Type</label>
|
||||
<select id="model-type" name="model-type" class="form-control" data-width='100%'>
|
||||
<option disabled selected>Select Model Type</option>
|
||||
@ -32,12 +38,12 @@
|
||||
<option value="{{ v }}">{{ k }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<!-- ML and Rule Based Based Form-->
|
||||
<div id="package-based-relative-reasoning-specific-form">
|
||||
|
||||
<!-- Rule Packages -->
|
||||
<label for="package-based-relative-reasoning-rule-packages">Rule Packages</label>
|
||||
<select id="package-based-relative-reasoning-rule-packages" name="package-based-relative-reasoning-rule-packages"
|
||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
||||
<div id="rule-packages" class="ep-model-param mlrr rbrr">
|
||||
<label for="model-rule-packages">Rule Packages</label>
|
||||
<select id="model-rule-packages" name="model-rule-packages" data-actions-box='true'
|
||||
class="form-control" multiple data-width='100%'>
|
||||
<option disabled>Reviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if obj.reviewed %}
|
||||
@ -52,10 +58,13 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Data Packages -->
|
||||
<label for="package-based-relative-reasoning-data-packages" >Data Packages</label>
|
||||
<select id="package-based-relative-reasoning-data-packages" name="package-based-relative-reasoning-data-packages"
|
||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
||||
<div id="data-packages" class="ep-model-param mlrr rbrr enviformer">
|
||||
<label for="model-data-packages">Data Packages</label>
|
||||
<select id="model-data-packages" name="model-data-packages" data-actions-box='true'
|
||||
class="form-control" multiple data-width='100%'>
|
||||
<option disabled>Reviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if obj.reviewed %}
|
||||
@ -70,32 +79,31 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="ml-relative-reasoning-specific-form">
|
||||
<!-- Fingerprinter -->
|
||||
<label for="ml-relative-reasoning-fingerprinter">Fingerprinter</label>
|
||||
<select id="ml-relative-reasoning-fingerprinter" name="ml-relative-reasoning-fingerprinter"
|
||||
class="form-control">
|
||||
<div id="fingerprinter" class="ep-model-param mlrr">
|
||||
<label for="model-fingerprinter">Fingerprinter</label>
|
||||
<select id="model-fingerprinter" name="model-fingerprinter" data-actions-box='true'
|
||||
class="form-control" multiple data-width='100%'>
|
||||
<option value="MACCS" selected>MACCS Fingerprinter</option>
|
||||
</select>
|
||||
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
|
||||
<!-- Property Plugins go here -->
|
||||
<label for="ml-relative-reasoning-additional-fingerprinter">Additional Fingerprinter /
|
||||
Descriptors</label>
|
||||
<select id="ml-relative-reasoning-additional-fingerprinter"
|
||||
name="ml-relative-reasoning-additional-fingerprinter" class="form-control">
|
||||
<option disabled selected>Select Additional Fingerprinter / Descriptor</option>
|
||||
{% for k, v in additional_descriptors.items %}
|
||||
<option value="{{ v }}">{{ k }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
|
||||
<label for="ml-relative-reasoning-threshold">Threshold</label>
|
||||
<input type="number" min="0" max="1" step="0.05" value="0.5"
|
||||
id="ml-relative-reasoning-threshold"
|
||||
name="ml-relative-reasoning-threshold" class="form-control">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Threshold -->
|
||||
<div id="threshold" class="ep-model-param mlrr enviformer">
|
||||
<label for="model-threshold">Threshold</label>
|
||||
<input type="number" min="0" max="1" step="0.05" value="0.5" id="model-threshold"
|
||||
name="model-threshold" class="form-control">
|
||||
</div>
|
||||
|
||||
<div id="appdomain" class="ep-model-param mlrr">
|
||||
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
|
||||
<!-- Build AD? -->
|
||||
<div class="checkbox">
|
||||
@ -107,11 +115,13 @@
|
||||
<div id="ad-params" style="display:none">
|
||||
<!-- Num Neighbors -->
|
||||
<label for="num-neighbors">Number of Neighbors</label>
|
||||
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control" value="5"
|
||||
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control"
|
||||
value="5"
|
||||
step="1" min="0" max="10">
|
||||
<!-- Local Compatibility -->
|
||||
<label for="local-compatibility-threshold">Local Compatibility Threshold</label>
|
||||
<input id="local-compatibility-threshold" name="local-compatibility-threshold" type="number"
|
||||
<input id="local-compatibility-threshold" name="local-compatibility-threshold"
|
||||
type="number"
|
||||
class="form-control" value="0.5" step="0.01" min="0" max="1">
|
||||
<!-- Reliability -->
|
||||
<label for="reliability-threshold">Reliability Threshold</label>
|
||||
@ -120,12 +130,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- EnviFormer-->
|
||||
<div id="enviformer-specific-form">
|
||||
<label for="enviformer-threshold">Threshold</label>
|
||||
<input type="number" min="0" max="1" step="0.05" value="0.5" id="enviformer-threshold"
|
||||
name="enviformer-threshold" class="form-control">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@ -137,20 +141,23 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$(function () {
|
||||
// Built in Model Types
|
||||
var nativeModelTypes = [
|
||||
"mlrr",
|
||||
"rbrr",
|
||||
"enviformer",
|
||||
]
|
||||
|
||||
// Initially hide all "specific" forms
|
||||
$("div[id$='-specific-form']").each( function() {
|
||||
$(".ep-model-param").each(function () {
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
$('#model-type').selectpicker();
|
||||
$("#ml-relative-reasoning-fingerprinter").selectpicker();
|
||||
$("#package-based-relative-reasoning-rule-packages").selectpicker();
|
||||
$("#package-based-relative-reasoning-data-packages").selectpicker();
|
||||
$("#package-based-relative-reasoning-evaluation-packages").selectpicker();
|
||||
if ($('#ml-relative-reasoning-additional-fingerprinter').length > 0) {
|
||||
$("#ml-relative-reasoning-additional-fingerprinter").selectpicker();
|
||||
}
|
||||
$("#model-fingerprinter").selectpicker();
|
||||
$("#model-rule-packages").selectpicker();
|
||||
$("#model-data-packages").selectpicker();
|
||||
|
||||
$("#build-app-domain").change(function () {
|
||||
if ($(this).is(":checked")) {
|
||||
@ -161,29 +168,20 @@ $(function() {
|
||||
});
|
||||
|
||||
// On change hide all and show only selected
|
||||
$("#model-type").change(function() {
|
||||
$("div[id$='-specific-form']").each( function() {
|
||||
$(this).hide();
|
||||
});
|
||||
val = $('option:selected', this).val();
|
||||
|
||||
if (val === 'ml-relative-reasoning' || val === 'rule-based-relative-reasoning') {
|
||||
$("#package-based-relative-reasoning-specific-form").show();
|
||||
if (val === 'ml-relative-reasoning') {
|
||||
$("#ml-relative-reasoning-specific-form").show();
|
||||
}
|
||||
$("#model-type").change(function () {
|
||||
$('.ep-model-param').hide();
|
||||
var modelType = $('#model-type').val();
|
||||
if (nativeModelTypes.indexOf(modelType) !== -1) {
|
||||
$('.' + modelType).show();
|
||||
} else {
|
||||
$("#" + val + "-specific-form").show();
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
|
||||
$('#new_model_modal_form_submit').on('click', function(e){
|
||||
$('#new_model_modal_form_submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#new_model_form').submit();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -29,11 +29,11 @@
|
||||
<tr>
|
||||
<th>
|
||||
<input type="number" id="dateYear" name="scenario-date-year" class="form-control"
|
||||
placeholder="YYYY">
|
||||
placeholder="YYYY" max="{% now "Y" %}">
|
||||
</th>
|
||||
<th>
|
||||
<input type="number" id="dateMonth" name="scenario-date-month" min="1" max="12"
|
||||
class="form-control" placeholder="MM" align="">
|
||||
class="form-control" placeholder="MM" >
|
||||
</th>
|
||||
<th>
|
||||
<input type="number" id="dateDay" name="scenario-date-day" min="1" max="31" class="form-control"
|
||||
@ -88,8 +88,15 @@
|
||||
$('#new_scenario_form').submit();
|
||||
});
|
||||
|
||||
var dateYear = document.getElementById("dateYear");
|
||||
dateYear.addEventListener("change", () => {
|
||||
console.log("Final value after editing:", dateYear.value);
|
||||
if (dateYear.value.length < 4) {
|
||||
dateYear.value = {% now "Y" %};
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -15,12 +15,12 @@
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<label for="compound-structure-name">Name</label>
|
||||
<input id="compound-structure-name" class="form-control" name="compound-structure-name" value="{{ structure.name }}">
|
||||
<input id="compound-structure-name" class="form-control" name="compound-structure-name" value="{{ compound_structure.name }}">
|
||||
</p>
|
||||
<p>
|
||||
<label for="compound-structure-description">Description</label>
|
||||
<input id="compound-structure-description" type="text" class="form-control"
|
||||
value="{{ structure.description }}" name="compound-structure-description">
|
||||
value="{{ compound_structure.description }}" name="compound-structure-description">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -17,10 +17,10 @@
|
||||
For evaluation, you need to select the packages you want to use.
|
||||
While the model is evaluating, you can use the model for predictions.
|
||||
</div>
|
||||
<!-- Evaluation -->
|
||||
<label for="relative-reasoning-evaluation-packages">Evaluation Packages</label>
|
||||
<select id="relative-reasoning-evaluation-packages" name=relative-reasoning-evaluation-packages"
|
||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
||||
<!-- Evaluation Packages -->
|
||||
<label for="model-evaluation-packages">Evaluation Packages</label>
|
||||
<select id="model-evaluation-packages" name="model-evaluation-packages" data-actions-box='true'
|
||||
class="form-control" multiple data-width='100%'>
|
||||
<option disabled>Reviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if obj.reviewed %}
|
||||
@ -35,6 +35,15 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<!-- Eval Type -->
|
||||
<label for="model-evaluation-type">Evaluation Type</label>
|
||||
<select id="model-evaluation-type" name="model-evaluation-type" class="form-control">
|
||||
<option disabled selected>Select evaluation type</option>
|
||||
<option value="sg">Single Generation</option>
|
||||
<option value="mg">Multiple Generations</option>
|
||||
</select>
|
||||
|
||||
<input type="hidden" name="hidden" value="evaluate">
|
||||
</form>
|
||||
</div>
|
||||
@ -50,7 +59,7 @@
|
||||
|
||||
$(function () {
|
||||
|
||||
$("#relative-reasoning-evaluation-packages").selectpicker();
|
||||
$("#model-evaluation-packages").selectpicker();
|
||||
|
||||
$('#evaluate_model_form_submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
@ -23,6 +23,8 @@
|
||||
</select>
|
||||
<input type="hidden" name="hidden" value="copy">
|
||||
</form>
|
||||
<div id="copy-object-error-message" class="alert alert-danger" role="alert" style="display: none">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
@ -38,6 +40,7 @@
|
||||
|
||||
$('#generic-copy-object-modal-form-submit').click(function (e) {
|
||||
e.preventDefault();
|
||||
$('#copy-object-error-message').hide()
|
||||
|
||||
const packageUrl = $('#target-package').find(":selected").val();
|
||||
|
||||
@ -49,12 +52,22 @@
|
||||
object_to_copy: '{{ current_object.url }}',
|
||||
}
|
||||
|
||||
$.post(packageUrl, formData, function (response) {
|
||||
if (response.success) {
|
||||
window.location.href = response.success;
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
data: formData,
|
||||
url: packageUrl,
|
||||
success: function (data, textStatus) {
|
||||
window.location.href = data.success;
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
if (jqXHR.responseJSON.error.indexOf('to the same package') > -1) {
|
||||
$('#copy-object-error-message').append('<p>The target Package is the same as the source Package. Please select another target!</p>');
|
||||
} else {
|
||||
$('#copy-object-error-message').append('<p>' + jqXHR.responseJSON.error + '</p>');
|
||||
}
|
||||
$('#copy-object-error-message').show();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
169
templates/modals/objects/generic_set_aliases_modal.html
Normal file
169
templates/modals/objects/generic_set_aliases_modal.html
Normal file
@ -0,0 +1,169 @@
|
||||
{% load static %}
|
||||
|
||||
<style>
|
||||
.alias-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
cursor: text;
|
||||
min-height: 38px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.alias {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: #5bc0de;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
margin: 3px 3px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.alias .remove {
|
||||
margin-left: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.alias-input {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
border: none;
|
||||
outline: none;
|
||||
margin: 3px 3px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-control.alias-container {
|
||||
height: auto;
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal fade bs-modal-lg" id="set_aliases_modal" tabindex="-1" aria-labelledby="set_aliases_modal"
|
||||
aria-modal="true" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">Set Aliases for {{ current_object.name }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="set_aliases_modal_form" accept-charset="UTF-8" action="{{ current_object.url }}"
|
||||
data-remote="true" method="post">
|
||||
{% csrf_token %}
|
||||
<label for="alias-input">Aliases:</label>
|
||||
<div class="form-control alias-container" id="alias-box">
|
||||
{% for alias in current_object.aliases %}
|
||||
<span class="alias">{{ alias|escape }}<span class="remove">×</span></span>
|
||||
{% endfor %}
|
||||
<input type="text" id="alias-input" class="alias-input" placeholder="Add Alias...">
|
||||
</div>
|
||||
</form>
|
||||
<div id="add-alias-error-message" class="alert alert-danger" role="alert" style="display: none">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" id="set_aliases_modal_form_submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
$(function () {
|
||||
|
||||
function addAlias(aliasText) {
|
||||
aliasText = aliasText.trim();
|
||||
if (aliasText === '') return;
|
||||
|
||||
// Avoid duplicate aliass
|
||||
var exists = false;
|
||||
$('#alias-box .alias').each(function () {
|
||||
if ($(this).text().replace('×', '').trim().toLowerCase() === aliasText.toLowerCase()) {
|
||||
exists = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
var aliasHtml = '<span class="alias">' + $('<div>').text(aliasText).html() +
|
||||
'<span class="remove">×</span></span>';
|
||||
$(aliasHtml).insertBefore('#alias-input');
|
||||
}
|
||||
|
||||
$('#alias-input').val('');
|
||||
}
|
||||
|
||||
// Add alias when Enter is pressed
|
||||
$('#alias-input').on('keypress', function (e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
addAlias($(this).val());
|
||||
}
|
||||
});
|
||||
|
||||
// Add alias when input loses focus
|
||||
$('#alias-input').on('blur', function () {
|
||||
var val = $(this).val();
|
||||
if (val.trim() !== '') {
|
||||
addAlias(val);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove alias when clicking ×
|
||||
$('#alias-box').on('click', '.remove', function () {
|
||||
$(this).closest('.alias').remove();
|
||||
});
|
||||
|
||||
// Focus input when clicking the container
|
||||
$('#alias-box').on('click', function () {
|
||||
$('#alias-input').focus();
|
||||
});
|
||||
|
||||
|
||||
$('#set_aliases_modal_form_submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
let aliases = [];
|
||||
$('#alias-box .alias').each(function () {
|
||||
aliases.push($(this).text().replace('×', '').trim())
|
||||
});
|
||||
|
||||
if (aliases.length === 0) {
|
||||
// Set empty string for deletion of all aliases
|
||||
// If empty list is sent, its gets removed entirely from post data
|
||||
aliases = ['']
|
||||
}
|
||||
|
||||
formData = {
|
||||
'aliases': aliases
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
data: formData,
|
||||
url: '{{ current_object.url }}',
|
||||
traditional: true,
|
||||
success: function (data, textStatus) {
|
||||
window.location.href = data.success;
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
$('#add-alias-error-message').append('<p>Setting aliases failed!</p>');
|
||||
$('#add-alias-error-message').show(); }
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,60 @@
|
||||
{% load static %}
|
||||
<!-- Delete Object -->
|
||||
<div id="generic_set_external_reference_modal" class="modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">Add External References</h3>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="generic-set-external-reference-modal-form" accept-charset="UTF-8"
|
||||
action="{{ current_object.url }}"
|
||||
data-remote="true" method="post">
|
||||
{% csrf_token %}
|
||||
<label for="database-select">Select the Database you want to attach an External Reference
|
||||
for</label>
|
||||
<select id="database-select" name="selected-database" data-actions-box='true' class="form-control"
|
||||
data-width='100%'>
|
||||
<option disabled selected>Select Database</option>
|
||||
{% for entity, databases in meta.external_databases.items %}
|
||||
{% if entity == object_type %}
|
||||
{% for db in databases %}
|
||||
<option id="db-select-{{ db.database.pk }}" data-input-placeholder="{{ db.placeholder }}"
|
||||
value="{{ db.database.id }}">{{ db.database.name }}</option>`
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p></p>
|
||||
<div id="input-div" style="display: none">
|
||||
<label for="identifier" >The reference</label>
|
||||
<input type="text" id="identifier" name="identifier" class="form-control" placeholder="">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" id="generic-set-external-reference-modal-form-submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function () {
|
||||
|
||||
$("#database-select").on("change", function () {
|
||||
let selected = $(this).val();
|
||||
$("#identifier").attr("placeholder", $('#db-select-' + selected).data('input-placeholder'));
|
||||
$("#input-div").show();
|
||||
});
|
||||
|
||||
$('#generic-set-external-reference-modal-form-submit').click(function (e) {
|
||||
e.preventDefault();
|
||||
$('#generic-set-external-reference-modal-form').submit();
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
@ -18,6 +18,7 @@
|
||||
<select id="scenario-select" name="selected-scenarios" data-actions-box='true' class="form-control"
|
||||
multiple data-width='100%'>
|
||||
<option disabled>Select Scenarios</option>
|
||||
<option value="" hidden></option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
@ -64,6 +65,9 @@
|
||||
|
||||
$('#set_scenario_modal_form_submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
if ($('#scenario-select').val().length == 0) {
|
||||
$('#scenario-select').val("")
|
||||
}
|
||||
$('#set_scenario_modal_form').submit();
|
||||
});
|
||||
});
|
||||
|
||||
54
templates/modals/objects/identify_missing_rules_modal.html
Normal file
54
templates/modals/objects/identify_missing_rules_modal.html
Normal file
@ -0,0 +1,54 @@
|
||||
{% load static %}
|
||||
<!-- Identify Missing Rules -->
|
||||
<div id="identify_missing_rules_modal" class="modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">Identify Missing Rules</h3>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
By clicking on Download we'll search the Pathway for Reactions that are not backed by
|
||||
a Rule or which can be assembled by chaining two rules.
|
||||
<form id="identify-missing-rules-modal-form" accept-charset="UTF-8" action="{{ pathway.url }}"
|
||||
data-remote="true" method="GET">
|
||||
<label for="rule-package">Select the Rule Package</label>
|
||||
<select id="rule-package" name="rule-package" data-actions-box='true' class="form-control"
|
||||
data-width='100%'>
|
||||
<option disabled>Reviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if obj.reviewed %}
|
||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<option disabled>Unreviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if not obj.reviewed %}
|
||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="hidden" name="identify-missing-rules" value="true"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" id="identify-missing-rules-modal-submit">Download</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function () {
|
||||
|
||||
$('#identify-missing-rules-modal-submit').click(function (e) {
|
||||
e.preventDefault();
|
||||
$('#identify-missing-rules-modal-form').submit();
|
||||
$('#identify_missing_rules_modal').modal('hide');
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
@ -2,12 +2,13 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% block action_modals %}
|
||||
{% include "modals/objects/edit_rule_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
{% endblock action_modals %}
|
||||
{% block action_modals %}
|
||||
{% include "modals/objects/edit_rule_modal.html" %}
|
||||
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
{% endblock action_modals %}
|
||||
|
||||
<div class="panel-group" id="rule-detail">
|
||||
<div class="panel panel-default">
|
||||
@ -28,10 +29,27 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
{{ rule.description }}
|
||||
{{ rule.description|safe }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if rule.aliases %}
|
||||
<!-- Aliases -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="rule-aliases-link" data-toggle="collapse" data-parent="#rule-detail"
|
||||
href="#rule-aliases">Aliases</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="rule-aliases" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for alias in rule.aliases %}
|
||||
<a class="list-group-item">{{ alias }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Reaction Patterns -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
@ -69,6 +87,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if rule.enzymelinks %}
|
||||
<!-- EC Numbers -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
@ -76,12 +95,33 @@
|
||||
href="#rule-ec-numbers">EC Numbers</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="rule-ec-numbers" class="panel-collapse collapse">
|
||||
<div id="rule-ec-numbers" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
|
||||
{% for k, v in rule.get_grouped_enzymelinks.items %}
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="{{ k|slugify }}_Link" data-toggle="collapse"
|
||||
data-parent="#{{ k|slugify }}_Accordion"
|
||||
href="#{{ k|slugify }}">
|
||||
{{ k }}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="{{ k|slugify }}" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for enzyme in v %}
|
||||
<a class="list-group-item" href="{{ enzyme.url }}">
|
||||
{{ enzyme.ec_number }}
|
||||
<div style="position:absolute;bottom:10px;left:100px;">{{ enzyme.name }}</div>
|
||||
<div style="float:right;">{{ enzyme.linking_method }}</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@ -4,8 +4,10 @@
|
||||
|
||||
{% block action_modals %}
|
||||
{% include "modals/objects/edit_compound_modal.html" %}
|
||||
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||
{% include "modals/objects/add_structure_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/generic_set_external_reference_modal.html" %}
|
||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
{% endblock action_modals %}
|
||||
@ -36,6 +38,23 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if compound.aliases %}
|
||||
<!-- Aliases -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="compound-aliases-link" data-toggle="collapse" data-parent="#compound-detail"
|
||||
href="#compound-aliases">Aliases</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="compound-aliases" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for alias in compound.aliases %}
|
||||
<a class="list-group-item">{{ alias }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Description -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
@ -164,7 +183,7 @@
|
||||
</div>
|
||||
<div id="compound-external-identifier" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% if compound.get_pubchem_identifiers %}
|
||||
{% if compound.get_pubchem_compound_identifiers %}
|
||||
<div class="panel panel-default panel-heading list-group-item"
|
||||
style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
@ -174,12 +193,28 @@
|
||||
</h4>
|
||||
</div>
|
||||
<div id="compound-pubchem-identifier" class="panel-collapse collapse in">
|
||||
{% for eid in compound.get_pubchem_identifiers %}
|
||||
{% for eid in compound.get_pubchem_compound_identifiers %}
|
||||
<a class="list-group-item"
|
||||
href="{{ eid.external_url }}">CID{{ eid.identifier_value }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if compound.get_pubchem_substance_identifiers %}
|
||||
<div class="panel panel-default panel-heading list-group-item"
|
||||
style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="compound-pubchem-identifier-link" data-toggle="collapse"
|
||||
data-parent="#compound-external-identifier"
|
||||
href="#compound-pubchem-identifier">PubChem Substance Identifier</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="compound-pubchem-identifier" class="panel-collapse collapse in">
|
||||
{% for eid in compound.get_pubchem_substance_identifiers %}
|
||||
<a class="list-group-item"
|
||||
href="{{ eid.external_url }}">SID{{ eid.identifier_value }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if compound.get_chebi_identifiers %}
|
||||
<div class="panel panel-default panel-heading list-group-item"
|
||||
style="background-color:silver">
|
||||
|
||||
@ -2,11 +2,13 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% block action_modals %}
|
||||
{% include "modals/objects/edit_compound_structure_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
{% endblock action_modals %}
|
||||
{% block action_modals %}
|
||||
{% include "modals/objects/edit_compound_structure_modal.html" %}
|
||||
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/generic_set_external_reference_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
{% endblock action_modals %}
|
||||
|
||||
<div class="panel-group" id="compound-structure-detail">
|
||||
<div class="panel panel-default">
|
||||
@ -32,34 +34,52 @@
|
||||
<!-- Image -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="compound-image-link" data-toggle="collapse" data-parent="#compound-detail"
|
||||
href="#compound-image">Image Representation</a>
|
||||
<a id="compound-structure-image-link" data-toggle="collapse" data-parent="#compound-structure-detail"
|
||||
href="#compound-structure-image">Image Representation</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="compound-image" class="panel-collapse collapse in">
|
||||
<div id="compound-structure-image" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
<div id="image-div" align="center">
|
||||
{{ compound_structure.as_svg|safe }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SMILES -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="compound-smiles-link" data-toggle="collapse" data-parent="#compound-detail"
|
||||
href="#compound-smiles">SMILES Representation</a>
|
||||
<a id="compound-structure-smiles-link" data-toggle="collapse" data-parent="#compound-structure-detail"
|
||||
href="#compound-structure-smiles">SMILES Representation</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="compound-smiles" class="panel-collapse collapse in">
|
||||
<div id="compound-structure-smiles" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{{ compound_structure.smiles }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if compound_structure.aliases %}
|
||||
<!-- Aliases -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="compound-structure-aliases-link" data-toggle="collapse" data-parent="#compound-structure-detail"
|
||||
href="#compound-structure-aliases">Aliases</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="compound-structure-aliases" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for alias in compound_structure.aliases %}
|
||||
<a class="list-group-item">{{ alias }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if compound_structure.scenarios.all %}
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="compound_structure-scenario-link" data-toggle="collapse" data-parent="#compound-structure-detail"
|
||||
<a id="compound-structure-scenario-link" data-toggle="collapse" data-parent="#compound-structure-detail"
|
||||
href="#compound-structure-scenario">Scenarios</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
{% block action_modals %}
|
||||
{# {% include "modals/objects/edit_edge_modal.html" %}#}
|
||||
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
{% endblock action_modals %}
|
||||
@ -39,6 +40,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if edge.aliases %}
|
||||
<!-- Aliases -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="edge-aliases-link" data-toggle="collapse" data-parent="#edge-detail"
|
||||
href="#edge-aliases">Aliases</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="edge-aliases" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for alias in edge.aliases %}
|
||||
<a class="list-group-item">{{ alias }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Image -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
|
||||
105
templates/objects/enzymelink.html
Normal file
105
templates/objects/enzymelink.html
Normal file
@ -0,0 +1,105 @@
|
||||
{% extends "framework.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="panel-group" id="enzyme-detail">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||
{{ enzymelink.ec_number }}
|
||||
</div>
|
||||
|
||||
<!-- Name -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="enzyme-name-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||
href="#enzyme-name">Enzyme Name</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="enzyme-name" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{{ enzymelink.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Linking Method -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="enzyme-linking-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||
href="#enzyme-linking">Linking Method</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="enzyme-linking" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{{ enzymelink.linking_method }}. <a
|
||||
href="https://wiki.envipath.org/index.php/Rules#EnzymeLinks" target="#">Learn more >></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if enzymelink.kegg_reaction_links %}
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="enzyme-evidence-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||
href="#enzyme-evidence">Linking Evidence</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="enzyme-evidence" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for kl in enzymelink.kegg_reaction_links %}
|
||||
<a class="list-group-item"
|
||||
href="{{ kl.external_url }}">{{ kl.identifier_value }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if enzymelink.reaction_evidence.all %}
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="enzyme-reaction-evidence-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||
href="#enzyme-reaction-evidence">Linking Evidence - enviPath Reactions</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="enzyme-reaction-evidence" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for r in enzymelink.reaction_evidence.all %}
|
||||
<a class="list-group-item" href="{{ r.url }}">{{ r.name }} <i>({{ r.package.name }})</i></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if enzymelink.edge_evidence.all %}
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="enzyme-edge-evidence-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||
href="#enzyme-edge-evidence">Linking Evidence - enviPath Pathways</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="enzyme-edge-evidence" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for e in enzymelink.edge_evidence.all %}
|
||||
<a class="list-group-item" href="{{ e.pathway.url }}">{{ e.pathway.name }}
|
||||
<i>({{ r.package.name }})</i></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- External DB Reference -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="enzyme-external-identifier-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||
href="#enzyme-external-identifier">External DB References</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="enzyme-external-identifier" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
<a class="list-group-item"
|
||||
href="http://www.brenda-enzymes.org/enzyme.php?ecno={{ enzymelink.ec_number }}"
|
||||
target="_blank"> Brenda entry for {{ enzymelink.ec_number }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@ -117,7 +117,7 @@
|
||||
<!-- End Predict Panel -->
|
||||
{% endif %}
|
||||
|
||||
{% if model.app_domain %}
|
||||
{% if model.ready_for_prediction and model.app_domain %}
|
||||
<!-- App Domain -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
@ -315,12 +315,18 @@
|
||||
$("#predict-button").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
clear("predictResultTable");
|
||||
|
||||
data = {
|
||||
"smiles": $("#smiles-to-predict").val(),
|
||||
"classify": "ILikeCats!"
|
||||
}
|
||||
|
||||
clear("predictResultTable");
|
||||
if (data["smiles"].trim() === "") {
|
||||
$("#predictResultTable").addClass("alert alert-danger");
|
||||
$("#predictResultTable").append("Please enter a SMILES string to predict!");
|
||||
return;
|
||||
}
|
||||
|
||||
makeLoadingGif("#predictLoading", "{% static '/images/wait.gif' %}");
|
||||
$.ajax({
|
||||
@ -332,17 +338,17 @@
|
||||
$("#predictLoading").empty();
|
||||
handlePredictionResponse(data);
|
||||
} catch (error) {
|
||||
console.log("Error");
|
||||
|
||||
$("#predictLoading").empty();
|
||||
$("#predictResultTable").addClass("alert alert-danger");
|
||||
$("#predictResultTable").append("Error while processing request :/");
|
||||
$("#predictResultTable").append("Error while processing response :/");
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
error: function (jqXHR, textStatus, errorThrown, x) {
|
||||
$("#predictLoading").empty();
|
||||
$("#predictResultTable").addClass("alert alert-danger");
|
||||
$("#predictResultTable").append("Error while processing request :/");
|
||||
}
|
||||
$("#predictResultTable").append(jqXHR.responseJSON.error);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -351,12 +357,20 @@
|
||||
$("#assess-button").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
clear("appDomainAssessmentResultTable");
|
||||
|
||||
data = {
|
||||
"smiles": $("#smiles-to-assess").val(),
|
||||
"app-domain-assessment": "ILikeCats!"
|
||||
}
|
||||
|
||||
clear("appDomainAssessmentResultTable");
|
||||
if (data["smiles"].trim() === "") {
|
||||
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
||||
$("#appDomainAssessmentResultTable").append("Please enter a SMILES string to predict!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
makeLoadingGif("#appDomainLoading", "{% static '/images/wait.gif' %}");
|
||||
$.ajax({
|
||||
@ -369,16 +383,15 @@
|
||||
handleAssessmentResponse("{% url 'depict' %}", data);
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.log("Error");
|
||||
$("#appDomainLoading").empty();
|
||||
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
||||
$("#appDomainAssessmentResultTable").append("Error while processing request :/");
|
||||
$("#appDomainAssessmentResultTable").append("Error while processing response :/");
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
$("#appDomainLoading").empty();
|
||||
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
||||
$("#appDomainAssessmentResultTable").append("Error while processing request :/");
|
||||
$("#appDomainAssessmentResultTable").append(jqXHR.responseJSON.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
{% block action_modals %}
|
||||
{% include "modals/objects/edit_node_modal.html" %}
|
||||
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
{% endblock action_modals %}
|
||||
@ -42,6 +43,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if node.aliases %}
|
||||
<!-- Aliases -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="node-aliases-link" data-toggle="collapse" data-parent="#node-detail"
|
||||
href="#node-aliases">Aliases</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="node-aliases" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for alias in node.aliases %}
|
||||
<a class="list-group-item">{{ alias }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Image -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p> {{ package.description }} </p>
|
||||
<p> {{ package.description|safe }} </p>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
|
||||
@ -83,8 +83,10 @@
|
||||
{% include "modals/objects/add_pathway_edge_modal.html" %}
|
||||
{% include "modals/objects/download_pathway_csv_modal.html" %}
|
||||
{% include "modals/objects/download_pathway_image_modal.html" %}
|
||||
{% include "modals/objects/identify_missing_rules_modal.html" %}
|
||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||
{% include "modals/objects/edit_pathway_modal.html" %}
|
||||
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/delete_pathway_node_modal.html" %}
|
||||
{% include "modals/objects/delete_pathway_edge_modal.html" %}
|
||||
@ -176,9 +178,6 @@
|
||||
</nav>
|
||||
<div id="vizdiv" >
|
||||
<svg id="pwsvg">
|
||||
{% if debug %}
|
||||
<rect width="100%" height="100%" fill="aliceblue"/>
|
||||
{% endif %}
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6"
|
||||
orient="auto-start-reverse" markerUnits="userSpaceOnUse">
|
||||
@ -210,6 +209,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if pathway.aliases %}
|
||||
<!-- Aliases -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="pathway-aliases-link" data-toggle="collapse" data-parent="#pathway-detail"
|
||||
href="#pathway-aliases">Aliases</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="pathway-aliases" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for alias in pathway.aliases %}
|
||||
<a class="list-group-item">{{ alias }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if pathway.scenarios.all %}
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
|
||||
@ -4,8 +4,10 @@
|
||||
|
||||
{% block action_modals %}
|
||||
{% include "modals/objects/edit_reaction_modal.html" %}
|
||||
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||
{% include "modals/objects/generic_set_external_reference_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
{% endblock action_modals %}
|
||||
|
||||
@ -40,6 +42,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if reaction.aliases %}
|
||||
<!-- Aliases -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="reaction-aliases-link" data-toggle="collapse" data-parent="#reaction-detail"
|
||||
href="#reaction-aliases">Aliases</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="reaction-aliases" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for alias in reaction.aliases %}
|
||||
<a class="list-group-item">{{ alias }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Image -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
@ -105,6 +124,23 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if reaction.get_related_enzymes %}
|
||||
<!-- EC Numbers -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="rule-ec-numbers-link" data-toggle="collapse" data-parent="#rule-detail"
|
||||
href="#rule-ec-numbers">EC Numbers</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="rule-ec-numbers" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for e in reaction.get_related_enzymes %}
|
||||
<a class="list-group-item" href="http://www.brenda-enzymes.org/enzyme.php?ecno={{ e.ec_number }}">{{ e.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if reaction.related_pathways %}
|
||||
<!-- Pathways -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
{% block action_modals %}
|
||||
{% include "modals/objects/edit_rule_modal.html" %}
|
||||
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
@ -32,6 +33,23 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if rule.aliases %}
|
||||
<!-- Aliases -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="rule-aliases-link" data-toggle="collapse" data-parent="#rule-detail"
|
||||
href="#rule-aliases">Aliases</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="rule-aliases" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for alias in rule.aliases %}
|
||||
<a class="list-group-item">{{ alias }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Representation -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
@ -183,6 +201,43 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if rule.enzymelinks %}
|
||||
<!-- EC Numbers -->
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="rule-ec-numbers-link" data-toggle="collapse" data-parent="#rule-detail"
|
||||
href="#rule-ec-numbers">EC Numbers</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="rule-ec-numbers" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for k, v in rule.get_grouped_enzymelinks.items %}
|
||||
<div class="panel panel-default panel-heading list-group-item"
|
||||
style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="{{ k|slugify }}_Link" data-toggle="collapse"
|
||||
data-parent="#{{ k|slugify }}_Accordion"
|
||||
href="#{{ k|slugify }}">
|
||||
{{ k }}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="{{ k|slugify }}" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
{% for enzyme in v %}
|
||||
<a class="list-group-item" href="{{ enzyme.url }}">
|
||||
{{ enzyme.ec_number }}
|
||||
<div style="position:absolute;bottom:10px;left:100px;">{{ enzyme.name }}</div>
|
||||
<div style="float:right;">{{ enzyme.linking_method }}</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@ -13,91 +13,80 @@ class CompoundTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(CompoundTest, cls).setUpClass()
|
||||
cls.user = User.objects.get(username='anonymous')
|
||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
||||
cls.user = User.objects.get(username="anonymous")
|
||||
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||
|
||||
def test_smoke(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles='C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F',
|
||||
name='Afoxolaner',
|
||||
description='No Desc'
|
||||
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
name="Afoxolaner",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
self.assertEqual(c.default_structure.smiles,
|
||||
'C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F')
|
||||
self.assertEqual(c.name, 'Afoxolaner')
|
||||
self.assertEqual(c.description, 'No Desc')
|
||||
self.assertEqual(
|
||||
c.default_structure.smiles,
|
||||
"C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
)
|
||||
self.assertEqual(c.name, "Afoxolaner")
|
||||
self.assertEqual(c.description, "No Desc")
|
||||
|
||||
def test_missing_smiles(self):
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Compound.create(
|
||||
self.package,
|
||||
smiles=None,
|
||||
name='Afoxolaner',
|
||||
description='No Desc'
|
||||
)
|
||||
_ = Compound.create(self.package, smiles=None, name="Afoxolaner", description="No Desc")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Compound.create(
|
||||
self.package,
|
||||
smiles='',
|
||||
name='Afoxolaner',
|
||||
description='No Desc'
|
||||
)
|
||||
_ = Compound.create(self.package, smiles="", name="Afoxolaner", description="No Desc")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Compound.create(
|
||||
self.package,
|
||||
smiles=' ',
|
||||
name='Afoxolaner',
|
||||
description='No Desc'
|
||||
)
|
||||
_ = Compound.create(self.package, smiles=" ", name="Afoxolaner", description="No Desc")
|
||||
|
||||
def test_smiles_are_trimmed(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles=' C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F ',
|
||||
name='Afoxolaner',
|
||||
description='No Desc'
|
||||
smiles=" C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F ",
|
||||
name="Afoxolaner",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
self.assertEqual(c.default_structure.smiles,
|
||||
'C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F')
|
||||
self.assertEqual(
|
||||
c.default_structure.smiles,
|
||||
"C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
)
|
||||
|
||||
def test_name_and_description_optional(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles='C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F',
|
||||
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
)
|
||||
|
||||
self.assertEqual(c.name, 'Compound 1')
|
||||
self.assertEqual(c.description, 'no description')
|
||||
self.assertEqual(c.name, "Compound 1")
|
||||
self.assertEqual(c.description, "no description")
|
||||
|
||||
def test_empty_name_and_description_are_ignored(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles='C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F',
|
||||
name='',
|
||||
description='',
|
||||
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
name="",
|
||||
description="",
|
||||
)
|
||||
|
||||
self.assertEqual(c.name, 'Compound 1')
|
||||
self.assertEqual(c.description, 'no description')
|
||||
self.assertEqual(c.name, "Compound 1")
|
||||
self.assertEqual(c.description, "no description")
|
||||
|
||||
def test_deduplication(self):
|
||||
c1 = Compound.create(
|
||||
self.package,
|
||||
smiles='C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F',
|
||||
name='Afoxolaner',
|
||||
description='No Desc'
|
||||
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
name="Afoxolaner",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
c2 = Compound.create(
|
||||
self.package,
|
||||
smiles='C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F',
|
||||
name='Afoxolaner',
|
||||
description='No Desc'
|
||||
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
name="Afoxolaner",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
# Check if create detects that this Compound already exist
|
||||
@ -109,36 +98,36 @@ class CompoundTest(TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Compound.create(
|
||||
self.package,
|
||||
smiles='C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F',
|
||||
name='Afoxolaner',
|
||||
description='No Desc'
|
||||
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
name="Afoxolaner",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
def test_create_with_standardized_smiles(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
||||
name='Standardized SMILES',
|
||||
description='No Desc'
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="Standardized SMILES",
|
||||
description="No Desc",
|
||||
)
|
||||
self.assertEqual(len(c.structures.all()), 1)
|
||||
|
||||
cs = c.structures.all()[0]
|
||||
self.assertEqual(cs.normalized_structure, True)
|
||||
self.assertEqual(cs.smiles, 'O=C(O)C1=CC=C([N+](=O)[O-])C=C1')
|
||||
self.assertEqual(cs.smiles, "O=C(O)C1=CC=C([N+](=O)[O-])C=C1")
|
||||
|
||||
def test_create_with_non_standardized_smiles(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles='[O-][N+](=O)c1ccc(C(=O)[O-])cc1',
|
||||
name='Non Standardized SMILES',
|
||||
description='No Desc'
|
||||
smiles="[O-][N+](=O)c1ccc(C(=O)[O-])cc1",
|
||||
name="Non Standardized SMILES",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
self.assertEqual(len(c.structures.all()), 2)
|
||||
for cs in c.structures.all():
|
||||
if cs.normalized_structure:
|
||||
self.assertEqual(cs.smiles, 'O=C(O)C1=CC=C([N+](=O)[O-])C=C1')
|
||||
self.assertEqual(cs.smiles, "O=C(O)C1=CC=C([N+](=O)[O-])C=C1")
|
||||
break
|
||||
else:
|
||||
# Loop finished without break, lets fail...
|
||||
@ -147,51 +136,54 @@ class CompoundTest(TestCase):
|
||||
def test_add_structure_smoke(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
||||
name='Standardized SMILES',
|
||||
description='No Desc'
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="Standardized SMILES",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
c.add_structure('[O-][N+](=O)c1ccc(C(=O)[O-])cc1', 'Non Standardized SMILES')
|
||||
c.add_structure("[O-][N+](=O)c1ccc(C(=O)[O-])cc1", "Non Standardized SMILES")
|
||||
|
||||
self.assertEqual(len(c.structures.all()), 2)
|
||||
|
||||
def test_add_structure_with_different_normalized_smiles(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
||||
name='Standardized SMILES',
|
||||
description='No Desc'
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="Standardized SMILES",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
c.add_structure(
|
||||
'C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F',
|
||||
'Different Standardized SMILES')
|
||||
"C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
"Different Standardized SMILES",
|
||||
)
|
||||
|
||||
def test_delete(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
||||
name='Standardization Test',
|
||||
description='No Desc'
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="Standardization Test",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
c.delete()
|
||||
|
||||
self.assertEqual(Compound.objects.filter(package=self.package).count(), 0)
|
||||
self.assertEqual(CompoundStructure.objects.filter(compound__package=self.package).count(), 0)
|
||||
self.assertEqual(
|
||||
CompoundStructure.objects.filter(compound__package=self.package).count(), 0
|
||||
)
|
||||
|
||||
def test_set_as_default_structure(self):
|
||||
c1 = Compound.create(
|
||||
self.package,
|
||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
||||
name='Standardized SMILES',
|
||||
description='No Desc'
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="Standardized SMILES",
|
||||
description="No Desc",
|
||||
)
|
||||
default_structure = c1.default_structure
|
||||
|
||||
c2 = c1.add_structure('[O-][N+](=O)c1ccc(C(=O)[O-])cc1', 'Non Standardized SMILES')
|
||||
c2 = c1.add_structure("[O-][N+](=O)c1ccc(C(=O)[O-])cc1", "Non Standardized SMILES")
|
||||
|
||||
c1.set_default_structure(c2)
|
||||
self.assertNotEqual(default_structure, c2)
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from epdb.logic import PackageManager
|
||||
from epdb.models import Compound, User, Reaction
|
||||
@ -12,50 +11,47 @@ class CopyTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(CopyTest, cls).setUpClass()
|
||||
cls.user = User.objects.get(username='anonymous')
|
||||
cls.package = PackageManager.create_package(cls.user, 'Source Package', 'No Desc')
|
||||
cls.user = User.objects.get(username="anonymous")
|
||||
cls.package = PackageManager.create_package(cls.user, "Source Package", "No Desc")
|
||||
cls.AFOXOLANER = Compound.create(
|
||||
cls.package,
|
||||
smiles='C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F',
|
||||
name='Afoxolaner',
|
||||
description='Test compound for copying'
|
||||
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C4=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||
name="Afoxolaner",
|
||||
description="Test compound for copying",
|
||||
)
|
||||
|
||||
cls.FOUR_NITROBENZOIC_ACID = Compound.create(
|
||||
cls.package,
|
||||
smiles='[O-][N+](=O)c1ccc(C(=O)[O-])cc1', # Normalized: O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
||||
name='Test Compound',
|
||||
description='Compound with multiple structures'
|
||||
smiles="[O-][N+](=O)c1ccc(C(=O)[O-])cc1", # Normalized: O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
||||
name="Test Compound",
|
||||
description="Compound with multiple structures",
|
||||
)
|
||||
|
||||
cls.ETHANOL = Compound.create(
|
||||
cls.package,
|
||||
smiles='CCO',
|
||||
name='Ethanol',
|
||||
description='Simple alcohol'
|
||||
cls.package, smiles="CCO", name="Ethanol", description="Simple alcohol"
|
||||
)
|
||||
cls.target_package = PackageManager.create_package(cls.user, 'Target Package', 'No Desc')
|
||||
cls.target_package = PackageManager.create_package(cls.user, "Target Package", "No Desc")
|
||||
|
||||
cls.reaction_educt = Compound.create(
|
||||
cls.package,
|
||||
smiles='C(CCl)Cl',
|
||||
name='1,2-Dichloroethane',
|
||||
description='Eawag BBD compound c0001'
|
||||
smiles="C(CCl)Cl",
|
||||
name="1,2-Dichloroethane",
|
||||
description="Eawag BBD compound c0001",
|
||||
).default_structure
|
||||
|
||||
cls.reaction_product = Compound.create(
|
||||
cls.package,
|
||||
smiles='C(CO)Cl',
|
||||
name='2-Chloroethanol',
|
||||
description='Eawag BBD compound c0005'
|
||||
smiles="C(CO)Cl",
|
||||
name="2-Chloroethanol",
|
||||
description="Eawag BBD compound c0005",
|
||||
).default_structure
|
||||
|
||||
cls.REACTION = Reaction.create(
|
||||
package=cls.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=[cls.reaction_educt],
|
||||
products=[cls.reaction_product],
|
||||
multi_step=False
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
def test_compound_copy_basic(self):
|
||||
@ -68,7 +64,9 @@ class CopyTest(TestCase):
|
||||
self.assertEqual(self.AFOXOLANER.description, copied_compound.description)
|
||||
self.assertEqual(copied_compound.package, self.target_package)
|
||||
self.assertEqual(self.AFOXOLANER.package, self.package)
|
||||
self.assertEqual(self.AFOXOLANER.default_structure.smiles, copied_compound.default_structure.smiles)
|
||||
self.assertEqual(
|
||||
self.AFOXOLANER.default_structure.smiles, copied_compound.default_structure.smiles
|
||||
)
|
||||
|
||||
def test_compound_copy_with_multiple_structures(self):
|
||||
"""Test copying a compound with multiple structures"""
|
||||
@ -86,7 +84,7 @@ class CopyTest(TestCase):
|
||||
self.assertIsNotNone(copied_compound.default_structure)
|
||||
self.assertEqual(
|
||||
copied_compound.default_structure.smiles,
|
||||
self.FOUR_NITROBENZOIC_ACID.default_structure.smiles
|
||||
self.FOUR_NITROBENZOIC_ACID.default_structure.smiles,
|
||||
)
|
||||
|
||||
def test_compound_copy_preserves_aliases(self):
|
||||
@ -95,15 +93,15 @@ class CopyTest(TestCase):
|
||||
original_compound = self.ETHANOL
|
||||
|
||||
# Add aliases if the method exists
|
||||
if hasattr(original_compound, 'add_alias'):
|
||||
original_compound.add_alias('Ethyl alcohol')
|
||||
original_compound.add_alias('Grain alcohol')
|
||||
if hasattr(original_compound, "add_alias"):
|
||||
original_compound.add_alias("Ethyl alcohol")
|
||||
original_compound.add_alias("Grain alcohol")
|
||||
|
||||
mapping = dict()
|
||||
copied_compound = original_compound.copy(self.target_package, mapping)
|
||||
|
||||
# Verify aliases were copied if they exist
|
||||
if hasattr(original_compound, 'aliases') and hasattr(copied_compound, 'aliases'):
|
||||
if hasattr(original_compound, "aliases") and hasattr(copied_compound, "aliases"):
|
||||
original_aliases = original_compound.aliases
|
||||
copied_aliases = copied_compound.aliases
|
||||
self.assertEqual(original_aliases, copied_aliases)
|
||||
@ -113,10 +111,10 @@ class CopyTest(TestCase):
|
||||
original_compound = self.ETHANOL
|
||||
|
||||
# Add external identifiers if the methods exist
|
||||
if hasattr(original_compound, 'add_cas_number'):
|
||||
original_compound.add_cas_number('64-17-5')
|
||||
if hasattr(original_compound, 'add_pubchem_compound_id'):
|
||||
original_compound.add_pubchem_compound_id('702')
|
||||
if hasattr(original_compound, "add_cas_number"):
|
||||
original_compound.add_cas_number("64-17-5")
|
||||
if hasattr(original_compound, "add_pubchem_compound_id"):
|
||||
original_compound.add_pubchem_compound_id("702")
|
||||
|
||||
mapping = dict()
|
||||
copied_compound = original_compound.copy(self.target_package, mapping)
|
||||
@ -146,7 +144,9 @@ class CopyTest(TestCase):
|
||||
self.assertEqual(original_structure.smiles, copied_structure.smiles)
|
||||
self.assertEqual(original_structure.canonical_smiles, copied_structure.canonical_smiles)
|
||||
self.assertEqual(original_structure.inchikey, copied_structure.inchikey)
|
||||
self.assertEqual(original_structure.normalized_structure, copied_structure.normalized_structure)
|
||||
self.assertEqual(
|
||||
original_structure.normalized_structure, copied_structure.normalized_structure
|
||||
)
|
||||
|
||||
# Verify they are different objects
|
||||
self.assertNotEqual(original_structure.uuid, copied_structure.uuid)
|
||||
@ -177,7 +177,9 @@ class CopyTest(TestCase):
|
||||
self.assertEqual(orig_educt.compound.package, self.package)
|
||||
self.assertEqual(orig_educt.smiles, copy_educt.smiles)
|
||||
|
||||
for orig_product, copy_product in zip(self.REACTION.products.all(), copied_reaction.products.all()):
|
||||
for orig_product, copy_product in zip(
|
||||
self.REACTION.products.all(), copied_reaction.products.all()
|
||||
):
|
||||
self.assertNotEqual(orig_product.uuid, copy_product.uuid)
|
||||
self.assertEqual(orig_product.name, copy_product.name)
|
||||
self.assertEqual(orig_product.description, copy_product.description)
|
||||
|
||||
@ -11,21 +11,21 @@ class DatasetTest(TestCase):
|
||||
def setUp(self):
|
||||
self.cs1 = Compound.create(
|
||||
self.package,
|
||||
name='2,6-Dibromohydroquinone',
|
||||
description='http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1/compound/d6435251-1a54-4327-b4b1-fd6e9a8f4dc9/structure/d8a0225c-dbb5-4e6c-a642-730081c09c5b',
|
||||
smiles='C1=C(C(=C(C=C1O)Br)O)Br',
|
||||
name="2,6-Dibromohydroquinone",
|
||||
description="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1/compound/d6435251-1a54-4327-b4b1-fd6e9a8f4dc9/structure/d8a0225c-dbb5-4e6c-a642-730081c09c5b",
|
||||
smiles="C1=C(C(=C(C=C1O)Br)O)Br",
|
||||
).default_structure
|
||||
|
||||
self.cs2 = Compound.create(
|
||||
self.package,
|
||||
smiles='O=C(O)CC(=O)/C=C(/Br)C(=O)O',
|
||||
smiles="O=C(O)CC(=O)/C=C(/Br)C(=O)O",
|
||||
).default_structure
|
||||
|
||||
self.rule1 = Rule.create(
|
||||
rule_type='SimpleAmbitRule',
|
||||
rule_type="SimpleAmbitRule",
|
||||
package=self.package,
|
||||
smirks='[#8:8]([H])-[c:4]1[c:3]([H])[c:2](-[#1,#17,#35:9])[c:1](-[#8:7]([H]))[c:6](-[#1,#17,#35])[c:5]([H])1>>[#8-]-[#6:6](=O)-[#6:5]-[#6:4](=[O:8])\[#6:3]=[#6:2](\[#1,#17,#35:9])-[#6:1](-[#8-])=[O:7]',
|
||||
description='http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1/simple-ambit-rule/f6a56c0f-a4a0-4ee3-b006-d765b4767cf6'
|
||||
smirks="[#8:8]([H])-[c:4]1[c:3]([H])[c:2](-[#1,#17,#35:9])[c:1](-[#8:7]([H]))[c:6](-[#1,#17,#35])[c:5]([H])1>>[#8-]-[#6:6](=O)-[#6:5]-[#6:4](=[O:8])\\[#6:3]=[#6:2](\\[#1,#17,#35:9])-[#6:1](-[#8-])=[O:7]",
|
||||
description="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1/simple-ambit-rule/f6a56c0f-a4a0-4ee3-b006-d765b4767cf6",
|
||||
)
|
||||
|
||||
self.reaction1 = Reaction.create(
|
||||
@ -33,14 +33,14 @@ class DatasetTest(TestCase):
|
||||
educts=[self.cs1],
|
||||
products=[self.cs2],
|
||||
rules=[self.rule1],
|
||||
multi_step=False
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(DatasetTest, cls).setUpClass()
|
||||
cls.user = User.objects.get(username='anonymous')
|
||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
||||
cls.user = User.objects.get(username="anonymous")
|
||||
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||
|
||||
def test_smoke(self):
|
||||
reactions = [r for r in Reaction.objects.filter(package=self.package)]
|
||||
|
||||
88
tests/test_enviformer.py
Normal file
88
tests/test_enviformer.py
Normal file
@ -0,0 +1,88 @@
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from tempfile import TemporaryDirectory
|
||||
from django.test import TestCase, tag
|
||||
from epdb.logic import PackageManager
|
||||
from epdb.models import User, EnviFormer, Package, Setting
|
||||
from epdb.tasks import predict_simple, predict
|
||||
|
||||
|
||||
def measure_predict(mod, pathway_pk=None):
|
||||
# Measure and return the prediction time
|
||||
start = datetime.now()
|
||||
if pathway_pk:
|
||||
s = Setting()
|
||||
s.model = mod
|
||||
s.model_threshold = 0.2
|
||||
s.max_depth = 4
|
||||
s.max_nodes = 20
|
||||
s.save()
|
||||
pred_result = predict.delay(pathway_pk, s.pk, limit=s.max_depth)
|
||||
else:
|
||||
pred_result = predict_simple.delay(mod.pk, "C1=CC=C(CSCC2=CC=CC=C2)C=C1")
|
||||
_ = pred_result.get()
|
||||
return round((datetime.now() - start).total_seconds(), 2)
|
||||
|
||||
|
||||
@tag("slow")
|
||||
class EnviFormerTest(TestCase):
|
||||
fixtures = ["test_fixtures.jsonl.gz"]
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(EnviFormerTest, cls).setUpClass()
|
||||
cls.user = User.objects.get(username="anonymous")
|
||||
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||
cls.BBD_SUBSET = Package.objects.get(name="Fixtures")
|
||||
|
||||
def test_model_flow(self):
|
||||
"""Test the full flow of EnviFormer, dataset build -> model finetune -> model evaluate -> model inference"""
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
with self.settings(MODEL_DIR=tmpdir):
|
||||
threshold = float(0.5)
|
||||
data_package_objs = [self.BBD_SUBSET]
|
||||
eval_packages_objs = [self.BBD_SUBSET]
|
||||
mod = EnviFormer.create(
|
||||
self.package, data_package_objs, eval_packages_objs, threshold=threshold
|
||||
)
|
||||
|
||||
mod.build_dataset()
|
||||
mod.build_model()
|
||||
mod.evaluate_model(True, eval_packages_objs)
|
||||
|
||||
mod.predict("CCN(CC)C(=O)C1=CC(=CC=C1)C")
|
||||
|
||||
def test_predict_runtime(self):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
with self.settings(MODEL_DIR=tmpdir):
|
||||
threshold = float(0.5)
|
||||
data_package_objs = [self.BBD_SUBSET]
|
||||
eval_packages_objs = [self.BBD_SUBSET]
|
||||
mods = []
|
||||
for _ in range(4):
|
||||
mod = EnviFormer.create(
|
||||
self.package, data_package_objs, eval_packages_objs, threshold=threshold
|
||||
)
|
||||
mod.build_dataset()
|
||||
mod.build_model()
|
||||
mods.append(mod)
|
||||
|
||||
# Test prediction time drops after first prediction
|
||||
times = [measure_predict(mods[0]) for _ in range(5)]
|
||||
print(f"First prediction took {times[0]} seconds, subsequent ones took {times[1:]}")
|
||||
|
||||
# Test pathway prediction
|
||||
times = [measure_predict(mods[1], self.BBD_SUBSET.pathways[0].pk) for _ in range(5)]
|
||||
print(
|
||||
f"First pathway prediction took {times[0]} seconds, subsequent ones took {times[1:]}"
|
||||
)
|
||||
|
||||
# Test eviction by performing three prediction with every model, twice.
|
||||
times = defaultdict(list)
|
||||
for _ in range(
|
||||
2
|
||||
): # Eviction should cause the second iteration here to have to reload the models
|
||||
for mod in mods:
|
||||
for _ in range(3):
|
||||
times[mod.pk].append(measure_predict(mod))
|
||||
print(times)
|
||||
@ -4,8 +4,7 @@ from utilities.chem import FormatConverter
|
||||
|
||||
|
||||
class FormatConverterTestCase(TestCase):
|
||||
|
||||
def test_standardization(self):
|
||||
smiles = 'C[n+]1c([N-](C))cccc1'
|
||||
smiles = "C[n+]1c([N-](C))cccc1"
|
||||
standardized_smiles = FormatConverter.standardize(smiles)
|
||||
self.assertEqual(standardized_smiles, 'CN=C1C=CC=CN1C')
|
||||
self.assertEqual(standardized_smiles, "CN=C1C=CC=CN1C")
|
||||
|
||||
@ -4,7 +4,7 @@ import numpy as np
|
||||
from django.test import TestCase
|
||||
|
||||
from epdb.logic import PackageManager
|
||||
from epdb.models import User, MLRelativeReasoning, RuleBasedRelativeReasoning, Package
|
||||
from epdb.models import User, MLRelativeReasoning, Package
|
||||
|
||||
|
||||
class ModelTest(TestCase):
|
||||
@ -13,9 +13,9 @@ class ModelTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(ModelTest, cls).setUpClass()
|
||||
cls.user = User.objects.get(username='anonymous')
|
||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
||||
cls.BBD_SUBSET = Package.objects.get(name='Fixtures')
|
||||
cls.user = User.objects.get(username="anonymous")
|
||||
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||
cls.BBD_SUBSET = Package.objects.get(name="Fixtures")
|
||||
|
||||
def test_smoke(self):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
@ -24,16 +24,15 @@ class ModelTest(TestCase):
|
||||
|
||||
rule_package_objs = [self.BBD_SUBSET]
|
||||
data_package_objs = [self.BBD_SUBSET]
|
||||
eval_packages_objs = []
|
||||
eval_packages_objs = [self.BBD_SUBSET]
|
||||
|
||||
mod = MLRelativeReasoning.create(
|
||||
self.package,
|
||||
rule_package_objs,
|
||||
data_package_objs,
|
||||
eval_packages_objs,
|
||||
threshold=threshold,
|
||||
name='ECC - BBD - 0.5',
|
||||
description='Created MLRelativeReasoning in Testcase',
|
||||
name="ECC - BBD - 0.5",
|
||||
description="Created MLRelativeReasoning in Testcase",
|
||||
)
|
||||
|
||||
# mod = RuleBasedRelativeReasoning.create(
|
||||
@ -50,11 +49,9 @@ class ModelTest(TestCase):
|
||||
|
||||
mod.build_dataset()
|
||||
mod.build_model()
|
||||
mod.multigen_eval = True
|
||||
mod.save()
|
||||
# mod.evaluate_model()
|
||||
mod.evaluate_model(True, eval_packages_objs)
|
||||
|
||||
results = mod.predict('CCN(CC)C(=O)C1=CC(=CC=C1)C')
|
||||
results = mod.predict("CCN(CC)C(=O)C1=CC(=CC=C1)C")
|
||||
|
||||
products = dict()
|
||||
for r in results:
|
||||
@ -62,8 +59,11 @@ class ModelTest(TestCase):
|
||||
products[tuple(sorted(ps.product_set))] = (r.rule.name, r.probability)
|
||||
|
||||
expected = {
|
||||
('CC=O', 'CCNC(=O)C1=CC(C)=CC=C1'): ('bt0243-4301', np.float64(0.33333333333333337)),
|
||||
('CC1=CC=CC(C(=O)O)=C1', 'CCNCC'): ('bt0430-4011', np.float64(0.25)),
|
||||
("CC=O", "CCNC(=O)C1=CC(C)=CC=C1"): (
|
||||
"bt0243-4301",
|
||||
np.float64(0.33333333333333337),
|
||||
),
|
||||
("CC1=CC=CC(C(=O)O)=C1", "CCNCC"): ("bt0430-4011", np.float64(0.25)),
|
||||
}
|
||||
|
||||
self.assertEqual(products, expected)
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import json
|
||||
from django.test import TestCase
|
||||
from networkx.utils.misc import graphs_equal
|
||||
from epdb.logic import PackageManager, SPathway
|
||||
@ -12,9 +11,11 @@ class MultiGenTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(MultiGenTest, cls).setUpClass()
|
||||
cls.user: 'User' = User.objects.get(username='anonymous')
|
||||
cls.package: 'Package' = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
||||
cls.BBD_SUBSET: 'Package' = Package.objects.get(name='Fixtures')
|
||||
cls.user: "User" = User.objects.get(username="anonymous")
|
||||
cls.package: "Package" = PackageManager.create_package(
|
||||
cls.user, "Anon Test Package", "No Desc"
|
||||
)
|
||||
cls.BBD_SUBSET: "Package" = Package.objects.get(name="Fixtures")
|
||||
|
||||
def test_equal_pathways(self):
|
||||
"""Test that two identical pathways return a precision and recall of 1.0"""
|
||||
@ -23,14 +24,23 @@ class MultiGenTest(TestCase):
|
||||
if len(pathway.edge_set.all()) == 0: # Do not test pathways with no edges
|
||||
continue
|
||||
score, precision, recall = multigen_eval(pathway, pathway)
|
||||
self.assertEqual(precision, 1.0, f"Precision should be one for identical pathways. "
|
||||
f"Failed on pathway: {pathway.name}")
|
||||
self.assertEqual(recall, 1.0, f"Recall should be one for identical pathways. "
|
||||
f"Failed on pathway: {pathway.name}")
|
||||
self.assertEqual(
|
||||
precision,
|
||||
1.0,
|
||||
f"Precision should be one for identical pathways. "
|
||||
f"Failed on pathway: {pathway.name}",
|
||||
)
|
||||
self.assertEqual(
|
||||
recall,
|
||||
1.0,
|
||||
f"Recall should be one for identical pathways. Failed on pathway: {pathway.name}",
|
||||
)
|
||||
|
||||
def test_intermediates(self):
|
||||
"""Test that an intermediate can be correctly identified and the metrics are correctly adjusted"""
|
||||
score, precision, recall, intermediates = multigen_eval(*self.intermediate_case(), return_intermediates=True)
|
||||
score, precision, recall, intermediates = multigen_eval(
|
||||
*self.intermediate_case(), return_intermediates=True
|
||||
)
|
||||
self.assertEqual(len(intermediates), 1, "There should be 1 found intermediate")
|
||||
self.assertEqual(precision, 1, "Precision should be 1")
|
||||
self.assertEqual(recall, 1, "Recall should be 1")
|
||||
@ -49,7 +59,9 @@ class MultiGenTest(TestCase):
|
||||
|
||||
def test_all(self):
|
||||
"""Test an intermediate, false-positive and false-negative together"""
|
||||
score, precision, recall, intermediates = multigen_eval(*self.all_case(), return_intermediates=True)
|
||||
score, precision, recall, intermediates = multigen_eval(
|
||||
*self.all_case(), return_intermediates=True
|
||||
)
|
||||
self.assertEqual(len(intermediates), 1, "There should be 1 found intermediate")
|
||||
self.assertAlmostEqual(precision, 0.6, 3, "Precision should be 0.6")
|
||||
self.assertAlmostEqual(recall, 0.75, 3, "Recall should be 0.75")
|
||||
@ -57,19 +69,23 @@ class MultiGenTest(TestCase):
|
||||
def test_shallow_pathway(self):
|
||||
pathways = self.BBD_SUBSET.pathways.all()
|
||||
for pathway in pathways:
|
||||
pathway_name = pathway.name
|
||||
if len(pathway.edge_set.all()) == 0: # Do not test pathways with no edges
|
||||
continue
|
||||
|
||||
shallow_pathway = graph_from_pathway(SPathway.from_pathway(pathway))
|
||||
pathway = graph_from_pathway(pathway)
|
||||
|
||||
if not graphs_equal(shallow_pathway, pathway):
|
||||
print('\n\nS', shallow_pathway.adj)
|
||||
print('\n\nPW', pathway.adj)
|
||||
print("\n\nS", shallow_pathway.adj)
|
||||
print("\n\nPW", pathway.adj)
|
||||
# print(shallow_pathway.nodes, pathway.nodes)
|
||||
# print(shallow_pathway.graph, pathway.graph)
|
||||
|
||||
self.assertTrue(graphs_equal(shallow_pathway, pathway), f"Networkx graph from shallow pathway not "
|
||||
f"equal to pathway for pathway {pathway.name}")
|
||||
self.assertTrue(
|
||||
graphs_equal(shallow_pathway, pathway),
|
||||
f"Networkx graph from shallow pathway not "
|
||||
f"equal to pathway for pathway {pathway.name}",
|
||||
)
|
||||
|
||||
def test_graph_edit_eval(self):
|
||||
"""Performs all the previous tests but with graph_edit_eval
|
||||
@ -79,10 +95,16 @@ class MultiGenTest(TestCase):
|
||||
if len(pathway.edge_set.all()) == 0: # Do not test pathways with no edges
|
||||
continue
|
||||
score = pathway_edit_eval(pathway, pathway)
|
||||
self.assertEqual(score, 0.0, "Pathway edit distance should be zero for identical pathways. "
|
||||
f"Failed on pathway: {pathway.name}")
|
||||
self.assertEqual(
|
||||
score,
|
||||
0.0,
|
||||
"Pathway edit distance should be zero for identical pathways. "
|
||||
f"Failed on pathway: {pathway.name}",
|
||||
)
|
||||
inter_score = pathway_edit_eval(*self.intermediate_case())
|
||||
self.assertAlmostEqual(inter_score, 1.75, 3, "Pathway edit distance failed on intermediate case")
|
||||
self.assertAlmostEqual(
|
||||
inter_score, 1.75, 3, "Pathway edit distance failed on intermediate case"
|
||||
)
|
||||
fp_score = pathway_edit_eval(*self.fp_case())
|
||||
self.assertAlmostEqual(fp_score, 1.25, 3, "Pathway edit distance failed on fp case")
|
||||
fn_score = pathway_edit_eval(*self.fn_case())
|
||||
@ -93,22 +115,30 @@ class MultiGenTest(TestCase):
|
||||
def intermediate_case(self):
|
||||
"""Create an example with an intermediate in the predicted pathway"""
|
||||
true_pathway = Pathway.create(self.package, "CCO")
|
||||
true_pathway.add_edge([true_pathway.root_nodes.all()[0]], [true_pathway.add_node("CC(=O)O", depth=1)])
|
||||
true_pathway.add_edge(
|
||||
[true_pathway.root_nodes.all()[0]], [true_pathway.add_node("CC(=O)O", depth=1)]
|
||||
)
|
||||
pred_pathway = Pathway.create(self.package, "CCO")
|
||||
pred_pathway.add_edge([pred_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := pred_pathway.add_node("CC=O", depth=1)])
|
||||
pred_pathway.add_edge(
|
||||
[pred_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := pred_pathway.add_node("CC=O", depth=1)],
|
||||
)
|
||||
pred_pathway.add_edge([acetaldehyde], [pred_pathway.add_node("CC(=O)O", depth=2)])
|
||||
return true_pathway, pred_pathway
|
||||
|
||||
def fp_case(self):
|
||||
"""Create an example with an extra compound in the predicted pathway"""
|
||||
true_pathway = Pathway.create(self.package, "CCO")
|
||||
true_pathway.add_edge([true_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := true_pathway.add_node("CC=O", depth=1)])
|
||||
true_pathway.add_edge(
|
||||
[true_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := true_pathway.add_node("CC=O", depth=1)],
|
||||
)
|
||||
true_pathway.add_edge([acetaldehyde], [true_pathway.add_node("CC(=O)O", depth=2)])
|
||||
pred_pathway = Pathway.create(self.package, "CCO")
|
||||
pred_pathway.add_edge([pred_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := pred_pathway.add_node("CC=O", depth=1)])
|
||||
pred_pathway.add_edge(
|
||||
[pred_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := pred_pathway.add_node("CC=O", depth=1)],
|
||||
)
|
||||
pred_pathway.add_edge([acetaldehyde], [pred_pathway.add_node("CC(=O)O", depth=2)])
|
||||
pred_pathway.add_edge([acetaldehyde], [pred_pathway.add_node("C", depth=2)])
|
||||
return true_pathway, pred_pathway
|
||||
@ -116,22 +146,30 @@ class MultiGenTest(TestCase):
|
||||
def fn_case(self):
|
||||
"""Create an example with a missing compound in the predicted pathway"""
|
||||
true_pathway = Pathway.create(self.package, "CCO")
|
||||
true_pathway.add_edge([true_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := true_pathway.add_node("CC=O", depth=1)])
|
||||
true_pathway.add_edge(
|
||||
[true_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := true_pathway.add_node("CC=O", depth=1)],
|
||||
)
|
||||
true_pathway.add_edge([acetaldehyde], [true_pathway.add_node("CC(=O)O", depth=2)])
|
||||
pred_pathway = Pathway.create(self.package, "CCO")
|
||||
pred_pathway.add_edge([pred_pathway.root_nodes.all()[0]], [pred_pathway.add_node("CC=O", depth=1)])
|
||||
pred_pathway.add_edge(
|
||||
[pred_pathway.root_nodes.all()[0]], [pred_pathway.add_node("CC=O", depth=1)]
|
||||
)
|
||||
return true_pathway, pred_pathway
|
||||
|
||||
def all_case(self):
|
||||
"""Create an example with an intermediate, extra compound and missing compound"""
|
||||
true_pathway = Pathway.create(self.package, "CCO")
|
||||
true_pathway.add_edge([true_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := true_pathway.add_node("CC=O", depth=1)])
|
||||
true_pathway.add_edge(
|
||||
[true_pathway.root_nodes.all()[0]],
|
||||
[acetaldehyde := true_pathway.add_node("CC=O", depth=1)],
|
||||
)
|
||||
true_pathway.add_edge([acetaldehyde], [true_pathway.add_node("C", depth=2)])
|
||||
true_pathway.add_edge([acetaldehyde], [true_pathway.add_node("CC(=O)O", depth=2)])
|
||||
pred_pathway = Pathway.create(self.package, "CCO")
|
||||
pred_pathway.add_edge([pred_pathway.root_nodes.all()[0]], [methane := pred_pathway.add_node("C", depth=1)])
|
||||
pred_pathway.add_edge(
|
||||
[pred_pathway.root_nodes.all()[0]], [methane := pred_pathway.add_node("C", depth=1)]
|
||||
)
|
||||
pred_pathway.add_edge([methane], [true_pathway.add_node("CC=O", depth=2)])
|
||||
pred_pathway.add_edge([methane], [true_pathway.add_node("c1ccccc1", depth=2)])
|
||||
return true_pathway, pred_pathway
|
||||
|
||||
@ -10,127 +10,127 @@ class ReactionTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(ReactionTest, cls).setUpClass()
|
||||
cls.user = User.objects.get(username='anonymous')
|
||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
||||
cls.user = User.objects.get(username="anonymous")
|
||||
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||
|
||||
def test_smoke(self):
|
||||
educt = Compound.create(
|
||||
self.package,
|
||||
smiles='C(CCl)Cl',
|
||||
name='1,2-Dichloroethane',
|
||||
description='Eawag BBD compound c0001'
|
||||
smiles="C(CCl)Cl",
|
||||
name="1,2-Dichloroethane",
|
||||
description="Eawag BBD compound c0001",
|
||||
).default_structure
|
||||
|
||||
product = Compound.create(
|
||||
self.package,
|
||||
smiles='C(CO)Cl',
|
||||
name='2-Chloroethanol',
|
||||
description='Eawag BBD compound c0005'
|
||||
smiles="C(CO)Cl",
|
||||
name="2-Chloroethanol",
|
||||
description="Eawag BBD compound c0005",
|
||||
).default_structure
|
||||
|
||||
r = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=[educt],
|
||||
products=[product],
|
||||
multi_step=False
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
self.assertEqual(r.smirks(), 'C(CCl)Cl>>C(CO)Cl')
|
||||
self.assertEqual(r.name, 'Eawag BBD reaction r0001')
|
||||
self.assertEqual(r.description, 'no description')
|
||||
self.assertEqual(r.smirks(), "C(CCl)Cl>>C(CO)Cl")
|
||||
self.assertEqual(r.name, "Eawag BBD reaction r0001")
|
||||
self.assertEqual(r.description, "no description")
|
||||
|
||||
def test_string_educts_and_products(self):
|
||||
r = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
educts=['C(CCl)Cl'],
|
||||
products=['C(CO)Cl'],
|
||||
multi_step=False
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=["C(CCl)Cl"],
|
||||
products=["C(CO)Cl"],
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
self.assertEqual(r.smirks(), 'C(CCl)Cl>>C(CO)Cl')
|
||||
self.assertEqual(r.smirks(), "C(CCl)Cl>>C(CO)Cl")
|
||||
|
||||
def test_missing_smiles(self):
|
||||
educt = Compound.create(
|
||||
self.package,
|
||||
smiles='C(CCl)Cl',
|
||||
name='1,2-Dichloroethane',
|
||||
description='Eawag BBD compound c0001'
|
||||
smiles="C(CCl)Cl",
|
||||
name="1,2-Dichloroethane",
|
||||
description="Eawag BBD compound c0001",
|
||||
).default_structure
|
||||
|
||||
product = Compound.create(
|
||||
self.package,
|
||||
smiles='C(CO)Cl',
|
||||
name='2-Chloroethanol',
|
||||
description='Eawag BBD compound c0005'
|
||||
smiles="C(CO)Cl",
|
||||
name="2-Chloroethanol",
|
||||
description="Eawag BBD compound c0005",
|
||||
).default_structure
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=[educt],
|
||||
products=[],
|
||||
multi_step=False
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=[],
|
||||
products=[product],
|
||||
multi_step=False
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=[],
|
||||
products=[],
|
||||
multi_step=False
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
def test_empty_name_and_description_are_ignored(self):
|
||||
r = Reaction.create(
|
||||
package=self.package,
|
||||
name='',
|
||||
description='',
|
||||
educts=['C(CCl)Cl'],
|
||||
products=['C(CO)Cl'],
|
||||
name="",
|
||||
description="",
|
||||
educts=["C(CCl)Cl"],
|
||||
products=["C(CO)Cl"],
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
self.assertEqual(r.name, 'no name')
|
||||
self.assertEqual(r.description, 'no description')
|
||||
self.assertEqual(r.name, "no name")
|
||||
self.assertEqual(r.description, "no description")
|
||||
|
||||
def test_deduplication(self):
|
||||
rule = Rule.create(
|
||||
package=self.package,
|
||||
rule_type='SimpleAmbitRule',
|
||||
name='bt0022-2833',
|
||||
description='Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative',
|
||||
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
|
||||
rule_type="SimpleAmbitRule",
|
||||
name="bt0022-2833",
|
||||
description="Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative",
|
||||
smirks="[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]",
|
||||
)
|
||||
|
||||
r1 = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
educts=['C(CCl)Cl'],
|
||||
products=['C(CO)Cl'],
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=["C(CCl)Cl"],
|
||||
products=["C(CO)Cl"],
|
||||
rules=[rule],
|
||||
multi_step=False
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
r2 = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
educts=['C(CCl)Cl'],
|
||||
products=['C(CO)Cl'],
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=["C(CCl)Cl"],
|
||||
products=["C(CO)Cl"],
|
||||
rules=[rule],
|
||||
multi_step=False
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
# Check if create detects that this Compound already exist
|
||||
@ -141,18 +141,18 @@ class ReactionTest(TestCase):
|
||||
def test_deduplication_without_rules(self):
|
||||
r1 = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
educts=['C(CCl)Cl'],
|
||||
products=['C(CO)Cl'],
|
||||
multi_step=False
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=["C(CCl)Cl"],
|
||||
products=["C(CO)Cl"],
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
r2 = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
educts=['C(CCl)Cl'],
|
||||
products=['C(CO)Cl'],
|
||||
multi_step=False
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=["C(CCl)Cl"],
|
||||
products=["C(CO)Cl"],
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
# Check if create detects that this Compound already exist
|
||||
@ -164,19 +164,19 @@ class ReactionTest(TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
_ = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
educts=['ASDF'],
|
||||
products=['C(CO)Cl'],
|
||||
multi_step=False
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=["ASDF"],
|
||||
products=["C(CO)Cl"],
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
def test_delete(self):
|
||||
r = Reaction.create(
|
||||
package=self.package,
|
||||
name='Eawag BBD reaction r0001',
|
||||
educts=['C(CCl)Cl'],
|
||||
products=['C(CO)Cl'],
|
||||
multi_step=False
|
||||
name="Eawag BBD reaction r0001",
|
||||
educts=["C(CCl)Cl"],
|
||||
products=["C(CO)Cl"],
|
||||
multi_step=False,
|
||||
)
|
||||
|
||||
r.delete()
|
||||
|
||||
@ -10,73 +10,79 @@ class RuleTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(RuleTest, cls).setUpClass()
|
||||
cls.user = User.objects.get(username='anonymous')
|
||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
||||
cls.user = User.objects.get(username="anonymous")
|
||||
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||
|
||||
def test_smoke(self):
|
||||
r = Rule.create(
|
||||
rule_type='SimpleAmbitRule',
|
||||
rule_type="SimpleAmbitRule",
|
||||
package=self.package,
|
||||
name='bt0022-2833',
|
||||
description='Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative',
|
||||
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
|
||||
name="bt0022-2833",
|
||||
description="Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative",
|
||||
smirks="[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]",
|
||||
)
|
||||
|
||||
self.assertEqual(r.smirks,
|
||||
'[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]')
|
||||
self.assertEqual(r.name, 'bt0022-2833')
|
||||
self.assertEqual(r.description,
|
||||
'Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative')
|
||||
self.assertEqual(
|
||||
r.smirks,
|
||||
"[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]",
|
||||
)
|
||||
self.assertEqual(r.name, "bt0022-2833")
|
||||
self.assertEqual(
|
||||
r.description,
|
||||
"Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative",
|
||||
)
|
||||
|
||||
def test_smirks_are_trimmed(self):
|
||||
r = Rule.create(
|
||||
rule_type='SimpleAmbitRule',
|
||||
rule_type="SimpleAmbitRule",
|
||||
package=self.package,
|
||||
name='bt0022-2833',
|
||||
description='Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative',
|
||||
smirks=' [H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4] ',
|
||||
name="bt0022-2833",
|
||||
description="Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative",
|
||||
smirks=" [H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4] ",
|
||||
)
|
||||
|
||||
self.assertEqual(r.smirks,
|
||||
'[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]')
|
||||
self.assertEqual(
|
||||
r.smirks,
|
||||
"[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]",
|
||||
)
|
||||
|
||||
def test_name_and_description_optional(self):
|
||||
r = Rule.create(
|
||||
rule_type='SimpleAmbitRule',
|
||||
rule_type="SimpleAmbitRule",
|
||||
package=self.package,
|
||||
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
|
||||
smirks="[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]",
|
||||
)
|
||||
|
||||
self.assertRegex(r.name, 'Rule \\d+')
|
||||
self.assertEqual(r.description, 'no description')
|
||||
self.assertRegex(r.name, "Rule \\d+")
|
||||
self.assertEqual(r.description, "no description")
|
||||
|
||||
def test_empty_name_and_description_are_ignored(self):
|
||||
r = Rule.create(
|
||||
rule_type='SimpleAmbitRule',
|
||||
rule_type="SimpleAmbitRule",
|
||||
package=self.package,
|
||||
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
|
||||
name='',
|
||||
description='',
|
||||
smirks="[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]",
|
||||
name="",
|
||||
description="",
|
||||
)
|
||||
|
||||
self.assertRegex(r.name, 'Rule \\d+')
|
||||
self.assertEqual(r.description, 'no description')
|
||||
self.assertRegex(r.name, "Rule \\d+")
|
||||
self.assertEqual(r.description, "no description")
|
||||
|
||||
def test_deduplication(self):
|
||||
r1 = Rule.create(
|
||||
rule_type='SimpleAmbitRule',
|
||||
rule_type="SimpleAmbitRule",
|
||||
package=self.package,
|
||||
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
|
||||
name='',
|
||||
description='',
|
||||
smirks="[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]",
|
||||
name="",
|
||||
description="",
|
||||
)
|
||||
|
||||
r2 = Rule.create(
|
||||
rule_type='SimpleAmbitRule',
|
||||
rule_type="SimpleAmbitRule",
|
||||
package=self.package,
|
||||
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
|
||||
name='',
|
||||
description='',
|
||||
smirks="[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]",
|
||||
name="",
|
||||
description="",
|
||||
)
|
||||
|
||||
self.assertEqual(r1.pk, r2.pk)
|
||||
@ -84,21 +90,21 @@ class RuleTest(TestCase):
|
||||
|
||||
def test_valid_smirks(self):
|
||||
with self.assertRaises(ValueError):
|
||||
r = Rule.create(
|
||||
rule_type='SimpleAmbitRule',
|
||||
Rule.create(
|
||||
rule_type="SimpleAmbitRule",
|
||||
package=self.package,
|
||||
smirks='This is not a valid SMIRKS',
|
||||
name='',
|
||||
description='',
|
||||
smirks="This is not a valid SMIRKS",
|
||||
name="",
|
||||
description="",
|
||||
)
|
||||
|
||||
def test_delete(self):
|
||||
r = Rule.create(
|
||||
rule_type='SimpleAmbitRule',
|
||||
rule_type="SimpleAmbitRule",
|
||||
package=self.package,
|
||||
smirks='[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]',
|
||||
name='',
|
||||
description='',
|
||||
smirks="[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]",
|
||||
name="",
|
||||
description="",
|
||||
)
|
||||
|
||||
r.delete()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -12,34 +12,32 @@ class SimpleAmbitRuleTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(SimpleAmbitRuleTest, cls).setUpClass()
|
||||
cls.user = User.objects.get(username='anonymous')
|
||||
cls.package = PackageManager.create_package(cls.user, 'Simple Ambit Rule Test Package',
|
||||
'Test Package for SimpleAmbitRule')
|
||||
cls.user = User.objects.get(username="anonymous")
|
||||
cls.package = PackageManager.create_package(
|
||||
cls.user, "Simple Ambit Rule Test Package", "Test Package for SimpleAmbitRule"
|
||||
)
|
||||
|
||||
def test_create_basic_rule(self):
|
||||
"""Test creating a basic SimpleAmbitRule with minimal parameters."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks
|
||||
)
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||
|
||||
self.assertIsInstance(rule, SimpleAmbitRule)
|
||||
self.assertEqual(rule.smirks, smirks)
|
||||
self.assertEqual(rule.package, self.package)
|
||||
self.assertRegex(rule.name, r'Rule \d+')
|
||||
self.assertEqual(rule.description, 'no description')
|
||||
self.assertRegex(rule.name, r"Rule \d+")
|
||||
self.assertEqual(rule.description, "no description")
|
||||
self.assertIsNone(rule.reactant_filter_smarts)
|
||||
self.assertIsNone(rule.product_filter_smarts)
|
||||
|
||||
def test_create_with_all_parameters(self):
|
||||
"""Test creating SimpleAmbitRule with all parameters."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
name = 'Test Rule'
|
||||
description = 'A test biotransformation rule'
|
||||
reactant_filter = '[CH2X4]'
|
||||
product_filter = '[OH]'
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
name = "Test Rule"
|
||||
description = "A test biotransformation rule"
|
||||
reactant_filter = "[CH2X4]"
|
||||
product_filter = "[OH]"
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
@ -47,7 +45,7 @@ class SimpleAmbitRuleTest(TestCase):
|
||||
description=description,
|
||||
smirks=smirks,
|
||||
reactant_filter_smarts=reactant_filter,
|
||||
product_filter_smarts=product_filter
|
||||
product_filter_smarts=product_filter,
|
||||
)
|
||||
|
||||
self.assertEqual(rule.name, name)
|
||||
@ -60,127 +58,114 @@ class SimpleAmbitRuleTest(TestCase):
|
||||
"""Test that SMIRKS is required for rule creation."""
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
SimpleAmbitRule.create(package=self.package, smirks=None)
|
||||
self.assertIn('SMIRKS is required', str(cm.exception))
|
||||
self.assertIn("SMIRKS is required", str(cm.exception))
|
||||
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
SimpleAmbitRule.create(package=self.package, smirks='')
|
||||
self.assertIn('SMIRKS is required', str(cm.exception))
|
||||
SimpleAmbitRule.create(package=self.package, smirks="")
|
||||
self.assertIn("SMIRKS is required", str(cm.exception))
|
||||
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
SimpleAmbitRule.create(package=self.package, smirks=' ')
|
||||
self.assertIn('SMIRKS is required', str(cm.exception))
|
||||
SimpleAmbitRule.create(package=self.package, smirks=" ")
|
||||
self.assertIn("SMIRKS is required", str(cm.exception))
|
||||
|
||||
@patch('epdb.models.FormatConverter.is_valid_smirks')
|
||||
@patch("epdb.models.FormatConverter.is_valid_smirks")
|
||||
def test_invalid_smirks_validation(self, mock_is_valid):
|
||||
"""Test validation of SMIRKS format."""
|
||||
mock_is_valid.return_value = False
|
||||
|
||||
invalid_smirks = 'invalid_smirks_string'
|
||||
invalid_smirks = "invalid_smirks_string"
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=invalid_smirks
|
||||
)
|
||||
SimpleAmbitRule.create(package=self.package, smirks=invalid_smirks)
|
||||
|
||||
self.assertIn(f'SMIRKS "{invalid_smirks}" is invalid', str(cm.exception))
|
||||
mock_is_valid.assert_called_once_with(invalid_smirks)
|
||||
|
||||
def test_smirks_trimming(self):
|
||||
"""Test that SMIRKS strings are trimmed during creation."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
smirks_with_whitespace = f' {smirks} '
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
smirks_with_whitespace = f" {smirks} "
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks_with_whitespace
|
||||
)
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks_with_whitespace)
|
||||
|
||||
self.assertEqual(rule.smirks, smirks)
|
||||
|
||||
def test_empty_name_and_description_handling(self):
|
||||
"""Test that empty name and description are handled appropriately."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks,
|
||||
name='',
|
||||
description=' '
|
||||
package=self.package, smirks=smirks, name="", description=" "
|
||||
)
|
||||
|
||||
self.assertRegex(rule.name, r'Rule \d+')
|
||||
self.assertEqual(rule.description, 'no description')
|
||||
self.assertRegex(rule.name, r"Rule \d+")
|
||||
self.assertEqual(rule.description, "no description")
|
||||
|
||||
def test_deduplication_basic(self):
|
||||
"""Test that identical rules are deduplicated."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
|
||||
rule1 = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks,
|
||||
name='Rule 1'
|
||||
)
|
||||
rule1 = SimpleAmbitRule.create(package=self.package, smirks=smirks, name="Rule 1")
|
||||
|
||||
rule2 = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks,
|
||||
name='Rule 2' # Different name, but same SMIRKS
|
||||
name="Rule 2", # Different name, but same SMIRKS
|
||||
)
|
||||
|
||||
self.assertEqual(rule1.pk, rule2.pk)
|
||||
self.assertEqual(SimpleAmbitRule.objects.filter(package=self.package, smirks=smirks).count(), 1)
|
||||
self.assertEqual(
|
||||
SimpleAmbitRule.objects.filter(package=self.package, smirks=smirks).count(), 1
|
||||
)
|
||||
|
||||
def test_deduplication_with_filters(self):
|
||||
"""Test deduplication with filter SMARTS."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
reactant_filter = '[CH2X4]'
|
||||
product_filter = '[OH]'
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
reactant_filter = "[CH2X4]"
|
||||
product_filter = "[OH]"
|
||||
|
||||
rule1 = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks,
|
||||
reactant_filter_smarts=reactant_filter,
|
||||
product_filter_smarts=product_filter
|
||||
product_filter_smarts=product_filter,
|
||||
)
|
||||
|
||||
rule2 = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks,
|
||||
reactant_filter_smarts=reactant_filter,
|
||||
product_filter_smarts=product_filter
|
||||
product_filter_smarts=product_filter,
|
||||
)
|
||||
|
||||
self.assertEqual(rule1.pk, rule2.pk)
|
||||
|
||||
def test_no_deduplication_different_filters(self):
|
||||
"""Test that rules with different filters are not deduplicated."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
|
||||
rule1 = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks,
|
||||
reactant_filter_smarts='[CH2X4]'
|
||||
package=self.package, smirks=smirks, reactant_filter_smarts="[CH2X4]"
|
||||
)
|
||||
|
||||
rule2 = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks,
|
||||
reactant_filter_smarts='[CH3X4]'
|
||||
package=self.package, smirks=smirks, reactant_filter_smarts="[CH3X4]"
|
||||
)
|
||||
|
||||
self.assertNotEqual(rule1.pk, rule2.pk)
|
||||
self.assertEqual(SimpleAmbitRule.objects.filter(package=self.package, smirks=smirks).count(), 2)
|
||||
self.assertEqual(
|
||||
SimpleAmbitRule.objects.filter(package=self.package, smirks=smirks).count(), 2
|
||||
)
|
||||
|
||||
def test_filter_smarts_trimming(self):
|
||||
"""Test that filter SMARTS are trimmed and handled correctly."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
|
||||
# Test with whitespace-only filters (should be treated as None)
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks,
|
||||
reactant_filter_smarts=' ',
|
||||
product_filter_smarts=' '
|
||||
reactant_filter_smarts=" ",
|
||||
product_filter_smarts=" ",
|
||||
)
|
||||
|
||||
self.assertIsNone(rule.reactant_filter_smarts)
|
||||
@ -188,94 +173,85 @@ class SimpleAmbitRuleTest(TestCase):
|
||||
|
||||
def test_url_property(self):
|
||||
"""Test the URL property generation."""
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks='[H:1][C:2]>>[H:1][O:2]'
|
||||
)
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||
|
||||
expected_url = f'{self.package.url}/simple-ambit-rule/{rule.uuid}'
|
||||
expected_url = f"{self.package.url}/simple-ambit-rule/{rule.uuid}"
|
||||
self.assertEqual(rule.url, expected_url)
|
||||
|
||||
@patch('epdb.models.FormatConverter.apply')
|
||||
@patch("epdb.models.FormatConverter.apply")
|
||||
def test_apply_method(self, mock_apply):
|
||||
"""Test the apply method delegates to FormatConverter."""
|
||||
mock_apply.return_value = ['product1', 'product2']
|
||||
mock_apply.return_value = ["product1", "product2"]
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks='[H:1][C:2]>>[H:1][O:2]'
|
||||
)
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||
|
||||
test_smiles = 'CCO'
|
||||
test_smiles = "CCO"
|
||||
result = rule.apply(test_smiles)
|
||||
|
||||
mock_apply.assert_called_once_with(test_smiles, rule.smirks)
|
||||
self.assertEqual(result, ['product1', 'product2'])
|
||||
self.assertEqual(result, ["product1", "product2"])
|
||||
|
||||
def test_reactants_smarts_property(self):
|
||||
"""Test reactants_smarts property extracts correct part of SMIRKS."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
expected_reactants = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]'
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
expected_reactants = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]"
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks
|
||||
)
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||
|
||||
self.assertEqual(rule.reactants_smarts, expected_reactants)
|
||||
|
||||
def test_products_smarts_property(self):
|
||||
"""Test products_smarts property extracts correct part of SMIRKS."""
|
||||
smirks = '[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
expected_products = '[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]'
|
||||
smirks = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]>>[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
expected_products = "[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks=smirks
|
||||
)
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||
|
||||
self.assertEqual(rule.products_smarts, expected_products)
|
||||
|
||||
@patch('epdb.models.Package.objects')
|
||||
@patch("epdb.models.Package.objects")
|
||||
def test_related_reactions_property(self, mock_package_objects):
|
||||
"""Test related_reactions property returns correct queryset."""
|
||||
mock_qs = MagicMock()
|
||||
mock_package_objects.filter.return_value = mock_qs
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks='[H:1][C:2]>>[H:1][O:2]'
|
||||
)
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||
|
||||
# Instead of directly assigning, patch the property or use with patch.object
|
||||
with patch.object(type(rule), 'reaction_rule', new_callable=PropertyMock) as mock_reaction_rule:
|
||||
mock_reaction_rule.return_value.filter.return_value.order_by.return_value = ['reaction1', 'reaction2']
|
||||
with patch.object(
|
||||
type(rule), "reaction_rule", new_callable=PropertyMock
|
||||
) as mock_reaction_rule:
|
||||
mock_reaction_rule.return_value.filter.return_value.order_by.return_value = [
|
||||
"reaction1",
|
||||
"reaction2",
|
||||
]
|
||||
|
||||
result = rule.related_reactions
|
||||
|
||||
mock_package_objects.filter.assert_called_once_with(reviewed=True)
|
||||
mock_reaction_rule.return_value.filter.assert_called_once_with(package__in=mock_qs)
|
||||
mock_reaction_rule.return_value.filter.return_value.order_by.assert_called_once_with('name')
|
||||
self.assertEqual(result, ['reaction1', 'reaction2'])
|
||||
mock_reaction_rule.return_value.filter.return_value.order_by.assert_called_once_with(
|
||||
"name"
|
||||
)
|
||||
self.assertEqual(result, ["reaction1", "reaction2"])
|
||||
|
||||
@patch('epdb.models.Pathway.objects')
|
||||
@patch('epdb.models.Edge.objects')
|
||||
@patch("epdb.models.Pathway.objects")
|
||||
@patch("epdb.models.Edge.objects")
|
||||
def test_related_pathways_property(self, mock_edge_objects, mock_pathway_objects):
|
||||
"""Test related_pathways property returns correct queryset."""
|
||||
|
||||
mock_related_reactions = ['reaction1', 'reaction2']
|
||||
mock_related_reactions = ["reaction1", "reaction2"]
|
||||
|
||||
with patch.object(SimpleAmbitRule, "related_reactions", new_callable=PropertyMock) as mock_prop:
|
||||
with patch.object(
|
||||
SimpleAmbitRule, "related_reactions", new_callable=PropertyMock
|
||||
) as mock_prop:
|
||||
mock_prop.return_value = mock_related_reactions
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks='[H:1][C:2]>>[H:1][O:2]'
|
||||
)
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||
|
||||
# Mock Edge objects query
|
||||
mock_edge_values = MagicMock()
|
||||
mock_edge_values.values.return_value = ['pathway_id1', 'pathway_id2']
|
||||
mock_edge_values.values.return_value = ["pathway_id1", "pathway_id2"]
|
||||
mock_edge_objects.filter.return_value = mock_edge_values
|
||||
|
||||
# Mock Pathway objects query
|
||||
@ -285,52 +261,49 @@ class SimpleAmbitRuleTest(TestCase):
|
||||
result = rule.related_pathways
|
||||
|
||||
mock_edge_objects.filter.assert_called_once_with(edge_label__in=mock_related_reactions)
|
||||
mock_edge_values.values.assert_called_once_with('pathway_id')
|
||||
mock_edge_values.values.assert_called_once_with("pathway_id")
|
||||
mock_pathway_objects.filter.assert_called_once()
|
||||
self.assertEqual(result, mock_pathway_qs)
|
||||
|
||||
@patch('epdb.models.IndigoUtils.smirks_to_svg')
|
||||
@patch("epdb.models.IndigoUtils.smirks_to_svg")
|
||||
def test_as_svg_property(self, mock_smirks_to_svg):
|
||||
"""Test as_svg property calls IndigoUtils correctly."""
|
||||
mock_smirks_to_svg.return_value = '<svg>test_svg</svg>'
|
||||
mock_smirks_to_svg.return_value = "<svg>test_svg</svg>"
|
||||
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks='[H:1][C:2]>>[H:1][O:2]'
|
||||
)
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||
|
||||
result = rule.as_svg
|
||||
mock_smirks_to_svg.assert_called_once_with(rule.smirks, True, width=800, height=400)
|
||||
self.assertEqual(result, '<svg>test_svg</svg>')
|
||||
self.assertEqual(result, "<svg>test_svg</svg>")
|
||||
|
||||
def test_atomic_transaction(self):
|
||||
"""Test that rule creation is atomic."""
|
||||
smirks = '[H:1][C:2]>>[H:1][O:2]'
|
||||
smirks = "[H:1][C:2]>>[H:1][O:2]"
|
||||
|
||||
# This should work normally
|
||||
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||
self.assertIsInstance(rule, SimpleAmbitRule)
|
||||
|
||||
# Test transaction rollback on error
|
||||
with patch('epdb.models.SimpleAmbitRule.save', side_effect=Exception('Database error')):
|
||||
with patch("epdb.models.SimpleAmbitRule.save", side_effect=Exception("Database error")):
|
||||
with self.assertRaises(Exception):
|
||||
SimpleAmbitRule.create(package=self.package, smirks='[H:3][C:4]>>[H:3][O:4]')
|
||||
SimpleAmbitRule.create(package=self.package, smirks="[H:3][C:4]>>[H:3][O:4]")
|
||||
|
||||
# Verify no partial data was saved
|
||||
self.assertEqual(SimpleAmbitRule.objects.filter(package=self.package).count(), 1)
|
||||
|
||||
def test_multiple_duplicate_warning(self):
|
||||
"""Test logging when multiple duplicates are found."""
|
||||
smirks = '[H:1][C:2]>>[H:1][O:2]'
|
||||
smirks = "[H:1][C:2]>>[H:1][O:2]"
|
||||
|
||||
# Create first rule
|
||||
rule1 = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||
|
||||
# Manually create a duplicate to simulate the error condition
|
||||
rule2 = SimpleAmbitRule(package=self.package, smirks=smirks, name='Manual Rule')
|
||||
rule2 = SimpleAmbitRule(package=self.package, smirks=smirks, name="Manual Rule")
|
||||
rule2.save()
|
||||
|
||||
with patch('epdb.models.logger') as mock_logger:
|
||||
with patch("epdb.models.logger") as mock_logger:
|
||||
# This should find the existing rule and log an error about multiple matches
|
||||
result = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||
|
||||
@ -339,24 +312,28 @@ class SimpleAmbitRuleTest(TestCase):
|
||||
|
||||
# Should log an error about multiple matches
|
||||
mock_logger.error.assert_called()
|
||||
self.assertIn('More than one rule matched', mock_logger.error.call_args[0][0])
|
||||
self.assertIn("More than one rule matched", mock_logger.error.call_args[0][0])
|
||||
|
||||
def test_model_fields(self):
|
||||
"""Test model field properties."""
|
||||
rule = SimpleAmbitRule.create(
|
||||
package=self.package,
|
||||
smirks='[H:1][C:2]>>[H:1][O:2]',
|
||||
reactant_filter_smarts='[CH3]',
|
||||
product_filter_smarts='[OH]'
|
||||
smirks="[H:1][C:2]>>[H:1][O:2]",
|
||||
reactant_filter_smarts="[CH3]",
|
||||
product_filter_smarts="[OH]",
|
||||
)
|
||||
|
||||
# Test field properties
|
||||
self.assertFalse(rule._meta.get_field('smirks').blank)
|
||||
self.assertFalse(rule._meta.get_field('smirks').null)
|
||||
self.assertTrue(rule._meta.get_field('reactant_filter_smarts').null)
|
||||
self.assertTrue(rule._meta.get_field('product_filter_smarts').null)
|
||||
self.assertFalse(rule._meta.get_field("smirks").blank)
|
||||
self.assertFalse(rule._meta.get_field("smirks").null)
|
||||
self.assertTrue(rule._meta.get_field("reactant_filter_smarts").null)
|
||||
self.assertTrue(rule._meta.get_field("product_filter_smarts").null)
|
||||
|
||||
# Test verbose names
|
||||
self.assertEqual(rule._meta.get_field('smirks').verbose_name, 'SMIRKS')
|
||||
self.assertEqual(rule._meta.get_field('reactant_filter_smarts').verbose_name, 'Reactant Filter SMARTS')
|
||||
self.assertEqual(rule._meta.get_field('product_filter_smarts').verbose_name, 'Product Filter SMARTS')
|
||||
self.assertEqual(rule._meta.get_field("smirks").verbose_name, "SMIRKS")
|
||||
self.assertEqual(
|
||||
rule._meta.get_field("reactant_filter_smarts").verbose_name, "Reactant Filter SMARTS"
|
||||
)
|
||||
self.assertEqual(
|
||||
rule._meta.get_field("product_filter_smarts").verbose_name, "Product Filter SMARTS"
|
||||
)
|
||||
|
||||
@ -1,32 +1,29 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from epdb.logic import SNode, SEdge, SPathway
|
||||
from epdb.logic import SNode, SEdge
|
||||
|
||||
|
||||
class SObjectTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_snode_eq(self):
|
||||
snode1 = SNode('CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O', 0)
|
||||
snode2 = SNode('CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O', 0)
|
||||
snode1 = SNode("CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O", 0)
|
||||
snode2 = SNode("CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O", 0)
|
||||
assert snode1 == snode2
|
||||
|
||||
def test_snode_hash(self):
|
||||
pass
|
||||
|
||||
def test_sedge_eq(self):
|
||||
sedge1 = SEdge([SNode('CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O', 0)],
|
||||
[SNode('CN1C(=O)NC2=C(C1=O)N(C)C=N2', 1), SNode('C=O', 1)],
|
||||
rule=None)
|
||||
sedge2 = SEdge([SNode('CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O', 0)],
|
||||
[SNode('CN1C(=O)NC2=C(C1=O)N(C)C=N2', 1), SNode('C=O', 1)],
|
||||
rule=None)
|
||||
sedge1 = SEdge(
|
||||
[SNode("CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O", 0)],
|
||||
[SNode("CN1C(=O)NC2=C(C1=O)N(C)C=N2", 1), SNode("C=O", 1)],
|
||||
rule=None,
|
||||
)
|
||||
sedge2 = SEdge(
|
||||
[SNode("CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O", 0)],
|
||||
[SNode("CN1C(=O)NC2=C(C1=O)N(C)C=N2", 1), SNode("C=O", 1)],
|
||||
rule=None,
|
||||
)
|
||||
assert sedge1 == sedge2
|
||||
|
||||
def test_sedge_hash(self):
|
||||
pass
|
||||
|
||||
def test_spathway(self):
|
||||
pw = SPathway()
|
||||
|
||||
396
tests/views/test_compound_views.py
Normal file
396
tests/views/test_compound_views.py
Normal file
@ -0,0 +1,396 @@
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from envipy_additional_information import Temperature, Interval
|
||||
|
||||
from epdb.logic import UserManager, PackageManager
|
||||
from epdb.models import Compound, Scenario, ExternalDatabase
|
||||
|
||||
|
||||
class CompoundViewTest(TestCase):
|
||||
fixtures = ["test_fixtures.jsonl.gz"]
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(CompoundViewTest, cls).setUpClass()
|
||||
cls.user1 = UserManager.create_user(
|
||||
"user1",
|
||||
"user1@envipath.com",
|
||||
"SuperSafe",
|
||||
set_setting=False,
|
||||
add_to_group=True,
|
||||
is_active=True,
|
||||
)
|
||||
cls.user1_default_package = cls.user1.default_package
|
||||
cls.package = PackageManager.create_package(cls.user1, "Test", "Test Pack")
|
||||
|
||||
def setUp(self):
|
||||
self.client.force_login(self.user1)
|
||||
|
||||
def test_create_compound(self):
|
||||
response = self.client.post(
|
||||
reverse("compounds"),
|
||||
{
|
||||
"compound-name": "1,2-Dichloroethane",
|
||||
"compound-description": "Eawag BBD compound c0001",
|
||||
"compound-smiles": "C(CCl)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
compound_url = response.url
|
||||
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
|
||||
self.assertEqual(c.package, self.user1_default_package)
|
||||
self.assertEqual(c.name, "1,2-Dichloroethane")
|
||||
self.assertEqual(c.description, "Eawag BBD compound c0001")
|
||||
self.assertEqual(c.default_structure.smiles, "C(CCl)Cl")
|
||||
self.assertEqual(c.default_structure.canonical_smiles, "ClCCCl")
|
||||
self.assertEqual(c.structures.all().count(), 2)
|
||||
self.assertEqual(self.user1_default_package.compounds.count(), 1)
|
||||
|
||||
# Adding the same rule again should return the existing one, hence not increasing the number of rules
|
||||
response = self.client.post(
|
||||
reverse("compounds"),
|
||||
{
|
||||
"compound-name": "1,2-Dichloroethane",
|
||||
"compound-description": "Eawag BBD compound c0001",
|
||||
"compound-smiles": "C(CCl)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.url, compound_url)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(self.user1_default_package.compounds.count(), 1)
|
||||
|
||||
# Adding the same rule in a different package should create a new rule
|
||||
response = self.client.post(
|
||||
reverse("package compound list", kwargs={"package_uuid": self.package.uuid}),
|
||||
{
|
||||
"compound-name": "1,2-Dichloroethane",
|
||||
"compound-description": "Eawag BBD compound c0001",
|
||||
"compound-smiles": "C(CCl)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertNotEqual(compound_url, response.url)
|
||||
|
||||
# adding another reaction should increase count
|
||||
response = self.client.post(
|
||||
reverse("compounds"),
|
||||
{
|
||||
"compound-name": "2-Chloroethanol",
|
||||
"compound-description": "Eawag BBD compound c0005",
|
||||
"compound-smiles": "C(CO)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(self.user1_default_package.compounds.count(), 2)
|
||||
|
||||
# Edit
|
||||
def test_edit_rule(self):
|
||||
response = self.client.post(
|
||||
reverse("compounds"),
|
||||
{
|
||||
"compound-name": "1,2-Dichloroethane",
|
||||
"compound-description": "Eawag BBD compound c0001",
|
||||
"compound-smiles": "C(CCl)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
compound_url = response.url
|
||||
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={
|
||||
"package_uuid": str(self.user1_default_package.uuid),
|
||||
"compound_uuid": str(c.uuid),
|
||||
},
|
||||
),
|
||||
{
|
||||
"compound-name": "Test Compound Adjusted",
|
||||
"compound-description": "New Description",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
self.assertEqual(c.name, "Test Compound Adjusted")
|
||||
self.assertEqual(c.description, "New Description")
|
||||
|
||||
# Rest stays untouched
|
||||
self.assertEqual(c.default_structure.smiles, "C(CCl)Cl")
|
||||
self.assertEqual(self.user1_default_package.compounds.count(), 1)
|
||||
|
||||
# Scenario
|
||||
def test_set_scenario(self):
|
||||
s1 = Scenario.create(
|
||||
self.user1_default_package,
|
||||
"Test Scen",
|
||||
"Test Desc",
|
||||
"2025-10",
|
||||
"soil",
|
||||
[Temperature(interval=Interval(start=20, end=30))],
|
||||
)
|
||||
|
||||
s2 = Scenario.create(
|
||||
self.user1_default_package,
|
||||
"Test Scen2",
|
||||
"Test Desc2",
|
||||
"2025-10",
|
||||
"soil",
|
||||
[Temperature(interval=Interval(start=10, end=20))],
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("compounds"),
|
||||
{
|
||||
"compound-name": "1,2-Dichloroethane",
|
||||
"compound-description": "Eawag BBD compound c0001",
|
||||
"compound-smiles": "C(CCl)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
compound_url = response.url
|
||||
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||
),
|
||||
{"selected-scenarios": [s1.url, s2.url]},
|
||||
)
|
||||
|
||||
self.assertEqual(len(c.scenarios.all()), 2)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||
),
|
||||
{"selected-scenarios": [s1.url]},
|
||||
)
|
||||
|
||||
self.assertEqual(len(c.scenarios.all()), 1)
|
||||
self.assertEqual(c.scenarios.first().url, s1.url)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||
),
|
||||
{
|
||||
# We have to set an empty string to avoid that the parameter is removed
|
||||
"selected-scenarios": ""
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(len(c.scenarios.all()), 0)
|
||||
|
||||
#
|
||||
def test_copy(self):
|
||||
response = self.client.post(
|
||||
reverse("compounds"),
|
||||
{
|
||||
"compound-name": "1,2-Dichloroethane",
|
||||
"compound-description": "Eawag BBD compound c0001",
|
||||
"compound-smiles": "C(CCl)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
compound_url = response.url
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package detail",
|
||||
kwargs={
|
||||
"package_uuid": str(self.package.uuid),
|
||||
},
|
||||
),
|
||||
{"hidden": "copy", "object_to_copy": c.url},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
copied_object_url = response.json()["success"]
|
||||
copied_compound = Compound.objects.get(url=copied_object_url)
|
||||
|
||||
self.assertEqual(copied_compound.name, c.name)
|
||||
self.assertEqual(copied_compound.description, c.description)
|
||||
self.assertEqual(copied_compound.default_structure.smiles, c.default_structure.smiles)
|
||||
|
||||
# Copy to the same package should fail
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package detail",
|
||||
kwargs={
|
||||
"package_uuid": str(c.package.uuid),
|
||||
},
|
||||
),
|
||||
{"hidden": "copy", "object_to_copy": c.url},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(
|
||||
response.json()["error"], f"Can't copy object {compound_url} to the same package!"
|
||||
)
|
||||
|
||||
def test_references(self):
|
||||
ext_db, _ = ExternalDatabase.objects.get_or_create(
|
||||
name="PubChem Compound",
|
||||
defaults={
|
||||
"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}",
|
||||
},
|
||||
)
|
||||
|
||||
ext_db2, _ = ExternalDatabase.objects.get_or_create(
|
||||
name="PubChem Substance",
|
||||
defaults={
|
||||
"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}",
|
||||
},
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("compounds"),
|
||||
{
|
||||
"compound-name": "1,2-Dichloroethane",
|
||||
"compound-description": "Eawag BBD compound c0001",
|
||||
"compound-smiles": "C(CCl)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
compound_url = response.url
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={
|
||||
"package_uuid": str(c.package.uuid),
|
||||
"compound_uuid": str(c.uuid),
|
||||
},
|
||||
),
|
||||
{"selected-database": ext_db.pk, "identifier": "25154249"},
|
||||
)
|
||||
|
||||
self.assertEqual(c.external_identifiers.count(), 1)
|
||||
self.assertEqual(c.external_identifiers.first().database, ext_db)
|
||||
self.assertEqual(c.external_identifiers.first().identifier_value, "25154249")
|
||||
self.assertEqual(
|
||||
c.external_identifiers.first().url, "https://pubchem.ncbi.nlm.nih.gov/compound/25154249"
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={
|
||||
"package_uuid": str(c.package.uuid),
|
||||
"compound_uuid": str(c.uuid),
|
||||
},
|
||||
),
|
||||
{"selected-database": ext_db2.pk, "identifier": "25154249"},
|
||||
)
|
||||
|
||||
self.assertEqual(c.external_identifiers.count(), 2)
|
||||
self.assertEqual(c.external_identifiers.last().database, ext_db2)
|
||||
self.assertEqual(c.external_identifiers.last().identifier_value, "25154249")
|
||||
self.assertEqual(
|
||||
c.external_identifiers.last().url, "https://pubchem.ncbi.nlm.nih.gov/substance/25154249"
|
||||
)
|
||||
|
||||
def test_delete(self):
|
||||
response = self.client.post(
|
||||
reverse("compounds"),
|
||||
{
|
||||
"compound-name": "1,2-Dichloroethane",
|
||||
"compound-description": "Eawag BBD compound c0001",
|
||||
"compound-smiles": "C(CCl)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
compound_url = response.url
|
||||
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||
),
|
||||
{"hidden": "delete"},
|
||||
)
|
||||
|
||||
self.assertEqual(self.user1_default_package.compounds.count(), 0)
|
||||
|
||||
def test_set_aliases(self):
|
||||
alias_1 = "Alias 1"
|
||||
alias_2 = "Alias 2"
|
||||
|
||||
response = self.client.post(
|
||||
reverse("compounds"),
|
||||
{
|
||||
"compound-name": "1,2-Dichloroethane",
|
||||
"compound-description": "Eawag BBD compound c0001",
|
||||
"compound-smiles": "C(CCl)Cl",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
compound_url = response.url
|
||||
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||
),
|
||||
{"aliases": [alias_1, alias_2]},
|
||||
)
|
||||
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
self.assertEqual(len(c.aliases), 2)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||
),
|
||||
{"aliases": [alias_1]},
|
||||
)
|
||||
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
self.assertEqual(len(c.aliases), 1)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"package compound detail",
|
||||
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||
),
|
||||
{
|
||||
# We have to set an empty string to avoid that the parameter is removed
|
||||
"aliases": ""
|
||||
},
|
||||
)
|
||||
|
||||
c = Compound.objects.get(url=compound_url)
|
||||
self.assertEqual(len(c.aliases), 0)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user