forked from enviPath/enviPy
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5ebb23622 | ||
|
|
93dd811e39 | ||
|
|
9a4735246f | ||
|
|
1f863fdcd6 | ||
|
|
1effaeb342 | ||
|
|
386098b8a6 | ||
|
|
ef697ac5f5 | ||
|
|
68a3f3b982 | ||
|
|
afeb56622c | ||
|
|
22f0bbe10b | ||
|
|
36879c266b | ||
|
|
c2c46fbfa7 | ||
|
|
d2f4fdc58a | ||
|
|
3f2b046bd6 | ||
|
|
7ad4112343 | ||
|
|
3f5bb76633 | ||
|
|
b757a07f91 |
@@ -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
|
# MAIL
|
||||||
EMAIL_HOST_USER=
|
EMAIL_HOST_USER=
|
||||||
EMAIL_HOST_PASSWORD=
|
EMAIL_HOST_PASSWORD=
|
||||||
|
|
||||||
+5
-1
@@ -5,4 +5,8 @@ static/admin/
|
|||||||
static/django_extensions/
|
static/django_extensions/
|
||||||
.env
|
.env
|
||||||
debug.log
|
debug.log
|
||||||
scratches/
|
scratches/
|
||||||
|
|
||||||
|
data/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -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
@@ -1 +1 @@
|
|||||||
3.10
|
3.12
|
||||||
|
|||||||
@@ -1,2 +1,91 @@
|
|||||||
# enviPy
|
# 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
|
||||||
|
```
|
||||||
|
|||||||
@@ -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.
|
# Django starts so that shared_task will use this app.
|
||||||
from .celery import app as celery_app
|
from .celery import app as celery_app
|
||||||
|
|
||||||
__all__ = ('celery_app',)
|
__all__ = ("celery_app",)
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ from ninja import NinjaAPI
|
|||||||
|
|
||||||
api = NinjaAPI()
|
api = NinjaAPI()
|
||||||
|
|
||||||
from ninja import NinjaAPI
|
|
||||||
|
|
||||||
api_v1 = NinjaAPI(title="API V1 Docs", urls_namespace="api-v1")
|
api_v1 = NinjaAPI(title="API V1 Docs", urls_namespace="api-v1")
|
||||||
api_legacy = NinjaAPI(title="Legacy API Docs", urls_namespace="api-legacy")
|
api_legacy = NinjaAPI(title="Legacy API Docs", urls_namespace="api-legacy")
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -11,6 +11,6 @@ import os
|
|||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
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()
|
application = get_asgi_application()
|
||||||
|
|||||||
+3
-3
@@ -4,15 +4,15 @@ from celery import Celery
|
|||||||
from celery.signals import setup_logging
|
from celery.signals import setup_logging
|
||||||
|
|
||||||
# Set the default Django settings module for the 'celery' program.
|
# 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
|
# Using a string here means the worker doesn't have to serialize
|
||||||
# the configuration object to child processes.
|
# the configuration object to child processes.
|
||||||
# - namespace='CELERY' means all celery-related configuration keys
|
# - namespace='CELERY' means all celery-related configuration keys
|
||||||
# should have a `CELERY_` prefix.
|
# 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
|
@setup_logging.connect
|
||||||
|
|||||||
+135
-144
@@ -9,6 +9,7 @@ https://docs.djangoproject.com/en/4.2/topics/settings/
|
|||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -20,34 +21,35 @@ from sklearn.tree import DecisionTreeClassifier
|
|||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
load_dotenv(BASE_DIR / '.env', override=False)
|
load_dotenv(BASE_DIR / ".env", override=False)
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# 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!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
|
DEBUG = os.environ.get("DEBUG", "False") == "True"
|
||||||
ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split(',')
|
|
||||||
|
ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(",")
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.staticfiles",
|
||||||
# 3rd party
|
# 3rd party
|
||||||
'django_extensions',
|
"django_extensions",
|
||||||
'oauth2_provider',
|
"oauth2_provider",
|
||||||
# Custom
|
# Custom
|
||||||
'epdb',
|
"epdb",
|
||||||
'migration',
|
"migration",
|
||||||
'epauth',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
@@ -55,42 +57,42 @@ AUTHENTICATION_BACKENDS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
"django.middleware.security.SecurityMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
'oauth2_provider.middleware.OAuth2TokenMiddleware',
|
"oauth2_provider.middleware.OAuth2TokenMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
OAUTH2_PROVIDER = {
|
OAUTH2_PROVIDER = {
|
||||||
"PKCE_REQUIRED": False, # Accept PKCE requests but dont require them
|
"PKCE_REQUIRED": False, # Accept PKCE requests but dont require them
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.environ.get('REGISTRATION_MANDATORY', False) == 'True':
|
if os.environ.get("REGISTRATION_MANDATORY", False) == "True":
|
||||||
MIDDLEWARE.append('epdb.middleware.login_required_middleware.LoginRequiredMiddleware')
|
MIDDLEWARE.append("epdb.middleware.login_required_middleware.LoginRequiredMiddleware")
|
||||||
|
|
||||||
ROOT_URLCONF = 'envipath.urls'
|
ROOT_URLCONF = "envipath.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': (os.path.join(BASE_DIR, 'templates'),),
|
"DIRS": (os.path.join(BASE_DIR, "templates"),),
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'envipath.wsgi.application'
|
WSGI_APPLICATION = "envipath.wsgi.application"
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
@@ -98,11 +100,11 @@ WSGI_APPLICATION = 'envipath.wsgi.application'
|
|||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"USER": os.environ['POSTGRES_USER'],
|
"USER": os.environ["POSTGRES_USER"],
|
||||||
"NAME": os.environ['POSTGRES_DB'],
|
"NAME": os.environ["POSTGRES_DB"],
|
||||||
"PASSWORD": os.environ['POSTGRES_PASSWORD'],
|
"PASSWORD": os.environ["POSTGRES_PASSWORD"],
|
||||||
"HOST": os.environ['POSTGRES_SERVICE_NAME'],
|
"HOST": os.environ["POSTGRES_SERVICE_NAME"],
|
||||||
"PORT": os.environ['POSTGRES_PORT']
|
"PORT": os.environ["POSTGRES_PORT"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,96 +112,87 @@ DATABASES = {
|
|||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
||||||
'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.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
# 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_I18N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
# 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:
|
if DEBUG:
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
else:
|
else:
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||||
EMAIL_USE_TLS = True
|
EMAIL_USE_TLS = True
|
||||||
EMAIL_HOST = 'mail.gandi.net'
|
EMAIL_HOST = "mail.gandi.net"
|
||||||
EMAIL_HOST_USER = os.environ['EMAIL_HOST_USER']
|
EMAIL_HOST_USER = os.environ["EMAIL_HOST_USER"]
|
||||||
EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD']
|
EMAIL_HOST_PASSWORD = os.environ["EMAIL_HOST_PASSWORD"]
|
||||||
EMAIL_PORT = 587
|
EMAIL_PORT = 587
|
||||||
|
DEFAULT_FROM_EMAIL = os.environ["DEFAULT_FROM_EMAIL"]
|
||||||
|
SERVER_EMAIL = os.environ["SERVER_EMAIL"]
|
||||||
|
|
||||||
AUTH_USER_MODEL = "epdb.User"
|
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
|
||||||
# SESAME_MAX_AGE = 300
|
# SESAME_MAX_AGE = 300
|
||||||
# # TODO set to "home"
|
# # TODO set to "home"
|
||||||
# LOGIN_REDIRECT_URL = "/"
|
# 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]
|
CSRF_TRUSTED_ORIGINS = [SERVER_URL]
|
||||||
|
|
||||||
AMBIT_URL = 'http://localhost:9001'
|
AMBIT_URL = "http://localhost:9001"
|
||||||
DEFAULT_VALUES = {
|
DEFAULT_VALUES = {"description": "no description"}
|
||||||
'description': 'no description'
|
|
||||||
}
|
|
||||||
|
|
||||||
EP_DATA_DIR = os.environ['EP_DATA_DIR']
|
EP_DATA_DIR = os.environ["EP_DATA_DIR"]
|
||||||
MODEL_DIR = os.path.join(EP_DATA_DIR, 'models')
|
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):
|
if not os.path.exists(MODEL_DIR):
|
||||||
os.mkdir(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):
|
if not os.path.exists(STATIC_DIR):
|
||||||
os.mkdir(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):
|
if not os.path.exists(LOG_DIR):
|
||||||
os.mkdir(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):
|
if not os.path.exists(PLUGIN_DIR):
|
||||||
os.mkdir(PLUGIN_DIR)
|
os.mkdir(PLUGIN_DIR)
|
||||||
|
|
||||||
# Set this as our static root dir
|
# Set this as our static root dir
|
||||||
STATIC_ROOT = STATIC_DIR
|
STATIC_ROOT = STATIC_DIR
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
# Where the sources are stored...
|
# Where the sources are stored...
|
||||||
STATICFILES_DIRS = (
|
STATICFILES_DIRS = (BASE_DIR / "static",)
|
||||||
BASE_DIR / 'static',
|
|
||||||
)
|
|
||||||
|
|
||||||
FIXTURE_DIRS = (
|
FIXTURE_DIRS = (BASE_DIR / "fixtures",)
|
||||||
BASE_DIR / 'fixtures',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
@@ -207,8 +200,8 @@ LOGGING = {
|
|||||||
"disable_existing_loggers": True,
|
"disable_existing_loggers": True,
|
||||||
"formatters": {
|
"formatters": {
|
||||||
"simple": {
|
"simple": {
|
||||||
'format': '[%(asctime)s] %(levelname)s %(module)s - %(message)s',
|
"format": "[%(asctime)s] %(levelname)s %(module)s - %(message)s",
|
||||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"handlers": {
|
"handlers": {
|
||||||
@@ -221,7 +214,7 @@ LOGGING = {
|
|||||||
"level": "DEBUG", # Or higher
|
"level": "DEBUG", # Or higher
|
||||||
"class": "logging.FileHandler",
|
"class": "logging.FileHandler",
|
||||||
"filename": os.path.join(LOG_DIR, "debug.log"),
|
"filename": os.path.join(LOG_DIR, "debug.log"),
|
||||||
"formatter": "simple"
|
"formatter": "simple",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"loggers": {
|
"loggers": {
|
||||||
@@ -229,72 +222,66 @@ LOGGING = {
|
|||||||
"epdb": {
|
"epdb": {
|
||||||
"handlers": ["file"], # "console",
|
"handlers": ["file"], # "console",
|
||||||
"propagate": True,
|
"propagate": True,
|
||||||
"level": os.environ.get('LOG_LEVEL', 'INFO')
|
"level": os.environ.get("LOG_LEVEL", "INFO"),
|
||||||
},
|
},
|
||||||
# For everything under envipath/ loaded via getlogger(__name__)
|
# For everything under envipath/ loaded via getlogger(__name__)
|
||||||
'envipath': {
|
"envipath": {
|
||||||
'handlers': ['file', 'console'],
|
"handlers": ["file", "console"],
|
||||||
'propagate': True,
|
"propagate": True,
|
||||||
'level': os.environ.get('LOG_LEVEL', 'INFO')
|
"level": os.environ.get("LOG_LEVEL", "INFO"),
|
||||||
},
|
},
|
||||||
# For everything under utilities/ loaded via getlogger(__name__)
|
# For everything under utilities/ loaded via getlogger(__name__)
|
||||||
'utilities': {
|
"utilities": {
|
||||||
'handlers': ['file', 'console'],
|
"handlers": ["file", "console"],
|
||||||
'propagate': True,
|
"propagate": True,
|
||||||
'level': os.environ.get('LOG_LEVEL', 'INFO')
|
"level": os.environ.get("LOG_LEVEL", "INFO"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Flags
|
# Flags
|
||||||
ENVIFORMER_PRESENT = os.environ.get('ENVIFORMER_PRESENT', 'False') == 'True'
|
ENVIFORMER_PRESENT = os.environ.get("ENVIFORMER_PRESENT", "False") == "True"
|
||||||
if ENVIFORMER_PRESENT:
|
ENVIFORMER_DEVICE = os.environ.get("ENVIFORMER_DEVICE", "cpu")
|
||||||
print("Loading enviFormer")
|
|
||||||
device = os.environ.get('ENVIFORMER_DEVICE', 'cpu')
|
|
||||||
from enviformer import load
|
|
||||||
ENVIFORMER_INSTANCE = load(device=device)
|
|
||||||
print("loaded")
|
|
||||||
|
|
||||||
|
|
||||||
# If celery is not present set always eager to true which will cause delayed tasks to block until finished
|
# 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:
|
if not FLAG_CELERY_PRESENT:
|
||||||
CELERY_TASK_ALWAYS_EAGER = True
|
CELERY_TASK_ALWAYS_EAGER = True
|
||||||
|
|
||||||
# Celery Configuration Options
|
# Celery Configuration Options
|
||||||
CELERY_TIMEZONE = "Europe/Berlin"
|
CELERY_TIMEZONE = "Europe/Berlin"
|
||||||
# Celery Configuration
|
# Celery Configuration
|
||||||
CELERY_BROKER_URL = 'redis://localhost:6379/0' # Use Redis as message broker
|
CELERY_BROKER_URL = "redis://localhost:6379/0" # Use Redis as message broker
|
||||||
CELERY_RESULT_BACKEND = 'redis://localhost:6379/1'
|
CELERY_RESULT_BACKEND = "redis://localhost:6379/1"
|
||||||
CELERY_ACCEPT_CONTENT = ['json']
|
CELERY_ACCEPT_CONTENT = ["json"]
|
||||||
CELERY_TASK_SERIALIZER = 'json'
|
CELERY_TASK_SERIALIZER = "json"
|
||||||
|
|
||||||
MODEL_BUILDING_ENABLED = os.environ.get('MODEL_BUILDING_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'
|
APPLICABILITY_DOMAIN_ENABLED = os.environ.get("APPLICABILITY_DOMAIN_ENABLED", "False") == "True"
|
||||||
DEFAULT_RF_MODEL_PARAMS = {
|
DEFAULT_RF_MODEL_PARAMS = {
|
||||||
'base_clf': RandomForestClassifier(
|
"base_clf": RandomForestClassifier(
|
||||||
n_estimators=100,
|
n_estimators=100,
|
||||||
max_features='log2',
|
max_features="log2",
|
||||||
random_state=42,
|
random_state=42,
|
||||||
criterion='entropy',
|
criterion="entropy",
|
||||||
ccp_alpha=0.0,
|
ccp_alpha=0.0,
|
||||||
max_depth=3,
|
max_depth=3,
|
||||||
min_samples_leaf=1
|
min_samples_leaf=1,
|
||||||
),
|
),
|
||||||
'num_chains': 10,
|
"num_chains": 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_MODEL_PARAMS = {
|
DEFAULT_MODEL_PARAMS = {
|
||||||
'base_clf': DecisionTreeClassifier(
|
"base_clf": DecisionTreeClassifier(
|
||||||
criterion='entropy',
|
criterion="entropy",
|
||||||
max_depth=3,
|
max_depth=3,
|
||||||
min_samples_split=5,
|
min_samples_split=5,
|
||||||
# min_samples_leaf=5,
|
# min_samples_leaf=5,
|
||||||
max_features='sqrt',
|
max_features="sqrt",
|
||||||
# class_weight='balanced',
|
# class_weight='balanced',
|
||||||
random_state=42
|
random_state=42,
|
||||||
),
|
),
|
||||||
'num_chains': 10,
|
"num_chains": 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_MAX_NUMBER_OF_NODES = 30
|
DEFAULT_MAX_NUMBER_OF_NODES = 30
|
||||||
@@ -302,9 +289,10 @@ DEFAULT_MAX_DEPTH = 5
|
|||||||
DEFAULT_MODEL_THRESHOLD = 0.25
|
DEFAULT_MODEL_THRESHOLD = 0.25
|
||||||
|
|
||||||
# Loading Plugins
|
# Loading Plugins
|
||||||
PLUGINS_ENABLED = os.environ.get('PLUGINS_ENABLED', 'False') == 'True'
|
PLUGINS_ENABLED = os.environ.get("PLUGINS_ENABLED", "False") == "True"
|
||||||
if PLUGINS_ENABLED:
|
if PLUGINS_ENABLED:
|
||||||
from utilities.plugin import discover_plugins
|
from utilities.plugin import discover_plugins
|
||||||
|
|
||||||
CLASSIFIER_PLUGINS = discover_plugins(_cls=Classifier)
|
CLASSIFIER_PLUGINS = discover_plugins(_cls=Classifier)
|
||||||
PROPERTY_PLUGINS = discover_plugins(_cls=Property)
|
PROPERTY_PLUGINS = discover_plugins(_cls=Property)
|
||||||
DESCRIPTOR_PLUGINS = discover_plugins(_cls=Descriptor)
|
DESCRIPTOR_PLUGINS = discover_plugins(_cls=Descriptor)
|
||||||
@@ -313,56 +301,59 @@ else:
|
|||||||
PROPERTY_PLUGINS = {}
|
PROPERTY_PLUGINS = {}
|
||||||
DESCRIPTOR_PLUGINS = {}
|
DESCRIPTOR_PLUGINS = {}
|
||||||
|
|
||||||
SENTRY_ENABLED = os.environ.get('SENTRY_ENABLED', 'False') == 'True'
|
SENTRY_ENABLED = os.environ.get("SENTRY_ENABLED", "False") == "True"
|
||||||
if SENTRY_ENABLED:
|
if SENTRY_ENABLED:
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
|
|
||||||
def before_send(event, hint):
|
def before_send(event, hint):
|
||||||
# Check if was a handled exception by one of our loggers
|
# Check if was a handled exception by one of our loggers
|
||||||
if event.get('logger'):
|
if event.get("logger"):
|
||||||
for log_path in LOGGING.get('loggers').keys():
|
for log_path in LOGGING.get("loggers").keys():
|
||||||
if event['logger'].startswith(log_path):
|
if event["logger"].startswith(log_path):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
sentry_sdk.init(
|
sentry_sdk.init(
|
||||||
dsn=os.environ.get('SENTRY_DSN'),
|
dsn=os.environ.get("SENTRY_DSN"),
|
||||||
# Add data like request headers and IP for users,
|
# Add data like request headers and IP for users,
|
||||||
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
|
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
|
||||||
send_default_pii=True,
|
send_default_pii=True,
|
||||||
environment=os.environ.get('SENTRY_ENVIRONMENT', 'development'),
|
environment=os.environ.get("SENTRY_ENVIRONMENT", "development"),
|
||||||
before_send=before_send,
|
before_send=before_send,
|
||||||
)
|
)
|
||||||
|
|
||||||
# compile into digestible flags
|
# compile into digestible flags
|
||||||
FLAGS = {
|
FLAGS = {
|
||||||
'MODEL_BUILDING': MODEL_BUILDING_ENABLED,
|
"MODEL_BUILDING": MODEL_BUILDING_ENABLED,
|
||||||
'CELERY': FLAG_CELERY_PRESENT,
|
"CELERY": FLAG_CELERY_PRESENT,
|
||||||
'PLUGINS': PLUGINS_ENABLED,
|
"PLUGINS": PLUGINS_ENABLED,
|
||||||
'SENTRY': SENTRY_ENABLED,
|
"SENTRY": SENTRY_ENABLED,
|
||||||
'ENVIFORMER': ENVIFORMER_PRESENT,
|
"ENVIFORMER": ENVIFORMER_PRESENT,
|
||||||
'APPLICABILITY_DOMAIN': APPLICABILITY_DOMAIN_ENABLED,
|
"APPLICABILITY_DOMAIN": APPLICABILITY_DOMAIN_ENABLED,
|
||||||
}
|
}
|
||||||
|
|
||||||
# path of the URL are checked via "startswith"
|
# path of the URL are checked via "startswith"
|
||||||
# -> /password_reset/done is covered as well
|
# -> /password_reset/done is covered as well
|
||||||
LOGIN_EXEMPT_URLS = [
|
LOGIN_EXEMPT_URLS = [
|
||||||
'/register',
|
"/register",
|
||||||
'/api/legacy/',
|
"/api/legacy/",
|
||||||
'/o/token/',
|
"/o/token/",
|
||||||
'/o/userinfo/',
|
"/o/userinfo/",
|
||||||
'/password_reset/',
|
"/password_reset/",
|
||||||
'/reset/',
|
"/reset/",
|
||||||
'/microsoft/',
|
"/microsoft/",
|
||||||
]
|
]
|
||||||
|
|
||||||
# MS AD/Entra
|
# 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:
|
if MS_ENTRA_ENABLED:
|
||||||
MS_ENTRA_CLIENT_ID = os.environ['MS_CLIENT_ID']
|
# Add app to installed apps
|
||||||
MS_ENTRA_CLIENT_SECRET = os.environ['MS_CLIENT_SECRET']
|
INSTALLED_APPS.append("epauth")
|
||||||
MS_ENTRA_TENANT_ID = os.environ['MS_TENANT_ID']
|
# 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_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}"
|
||||||
MS_ENTRA_REDIRECT_URI = os.environ['MS_REDIRECT_URI']
|
MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"]
|
||||||
MS_ENTRA_SCOPES = os.environ.get('MS_SCOPES', '').split(',')
|
MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",")
|
||||||
|
|||||||
+5
-1
@@ -14,13 +14,14 @@ Including another URLconf
|
|||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
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.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
|
||||||
from .api import api_v1, api_legacy
|
from .api import api_v1, api_legacy
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include("epauth.urls")),
|
|
||||||
path("", include("epdb.urls")),
|
path("", include("epdb.urls")),
|
||||||
path("", include("migration.urls")),
|
path("", include("migration.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
@@ -28,3 +29,6 @@ urlpatterns = [
|
|||||||
path("api/legacy/", api_legacy.urls),
|
path("api/legacy/", api_legacy.urls),
|
||||||
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if s.MS_ENTRA_ENABLED:
|
||||||
|
urlpatterns.append(path("", include("epauth.urls")))
|
||||||
|
|||||||
+1
-1
@@ -11,6 +11,6 @@ import os
|
|||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
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()
|
application = get_wsgi_application()
|
||||||
|
|||||||
+15
-2
@@ -16,7 +16,9 @@ from .models import (
|
|||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
Scenario,
|
Scenario,
|
||||||
Setting
|
Setting,
|
||||||
|
ExternalDatabase,
|
||||||
|
ExternalIdentifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -37,12 +39,13 @@ class GroupPackagePermissionAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
class EPAdmin(admin.ModelAdmin):
|
class EPAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['name', 'description']
|
search_fields = ["name", "description"]
|
||||||
|
|
||||||
|
|
||||||
class PackageAdmin(EPAdmin):
|
class PackageAdmin(EPAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MLRelativeReasoningAdmin(EPAdmin):
|
class MLRelativeReasoningAdmin(EPAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -87,6 +90,14 @@ class SettingAdmin(EPAdmin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalDatabaseAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalIdentifierAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
|
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
|
||||||
admin.site.register(Group, GroupAdmin)
|
admin.site.register(Group, GroupAdmin)
|
||||||
@@ -103,3 +114,5 @@ admin.site.register(Node, NodeAdmin)
|
|||||||
admin.site.register(Edge, EdgeAdmin)
|
admin.site.register(Edge, EdgeAdmin)
|
||||||
admin.site.register(Setting, SettingAdmin)
|
admin.site.register(Setting, SettingAdmin)
|
||||||
admin.site.register(Scenario, ScenarioAdmin)
|
admin.site.register(Scenario, ScenarioAdmin)
|
||||||
|
admin.site.register(ExternalDatabase, ExternalDatabaseAdmin)
|
||||||
|
admin.site.register(ExternalIdentifier, ExternalIdentifierAdmin)
|
||||||
|
|||||||
+9
-4
@@ -21,7 +21,7 @@ class BearerTokenAuth(HttpBearer):
|
|||||||
def _anonymous_or_real(request):
|
def _anonymous_or_real(request):
|
||||||
if request.user.is_authenticated and not request.user.is_anonymous:
|
if request.user.is_authenticated and not request.user.is_anonymous:
|
||||||
return request.user
|
return request.user
|
||||||
return get_user_model().objects.get(username='anonymous')
|
return get_user_model().objects.get(username="anonymous")
|
||||||
|
|
||||||
|
|
||||||
router = Router(auth=BearerTokenAuth())
|
router = Router(auth=BearerTokenAuth())
|
||||||
@@ -85,7 +85,9 @@ def get_package(request, package_uuid):
|
|||||||
try:
|
try:
|
||||||
return PackageManager.get_package_by_id(request.auth, package_id=package_uuid)
|
return PackageManager.get_package_by_id(request.auth, package_id=package_uuid)
|
||||||
except ValueError:
|
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})
|
@router.get("/compound", response={200: List[CompoundSchema], 403: Error})
|
||||||
@@ -97,7 +99,9 @@ def get_compounds(request):
|
|||||||
return qs
|
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
|
@paginate
|
||||||
def get_package_compounds(request, package_uuid):
|
def get_package_compounds(request, package_uuid):
|
||||||
try:
|
try:
|
||||||
@@ -105,4 +109,5 @@ def get_package_compounds(request, package_uuid):
|
|||||||
return Compound.objects.filter(package=p)
|
return Compound.objects.filter(package=p)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 403, {
|
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
-2
@@ -2,8 +2,8 @@ from django.apps import AppConfig
|
|||||||
|
|
||||||
|
|
||||||
class EPDBConfig(AppConfig):
|
class EPDBConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = 'epdb'
|
name = "epdb"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import epdb.signals # noqa: F401
|
import epdb.signals # noqa: F401
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
from django import forms
|
|
||||||
|
|
||||||
|
|
||||||
class EmailLoginForm(forms.Form):
|
|
||||||
email = forms.EmailField()
|
|
||||||
+722
-221
File diff suppressed because it is too large
Load Diff
+658
-389
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 django.db import transaction
|
||||||
|
|
||||||
from epdb.logic import UserManager, GroupManager, PackageManager, SettingManager
|
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):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def create_users(self):
|
def create_users(self):
|
||||||
|
|
||||||
# Anonymous User
|
# Anonymous User
|
||||||
if not User.objects.filter(email='anon@envipath.com').exists():
|
if not User.objects.filter(email="anon@envipath.com").exists():
|
||||||
anon = UserManager.create_user("anonymous", "anon@envipath.com", "SuperSafe",
|
anon = UserManager.create_user(
|
||||||
is_active=True, add_to_group=False, set_setting=False)
|
"anonymous",
|
||||||
|
"anon@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
is_active=True,
|
||||||
|
add_to_group=False,
|
||||||
|
set_setting=False,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
anon = User.objects.get(email='anon@envipath.com')
|
anon = User.objects.get(email="anon@envipath.com")
|
||||||
|
|
||||||
# Admin User
|
# Admin User
|
||||||
if not User.objects.filter(email='admin@envipath.com').exists():
|
if not User.objects.filter(email="admin@envipath.com").exists():
|
||||||
admin = UserManager.create_user("admin", "admin@envipath.com", "SuperSafe",
|
admin = UserManager.create_user(
|
||||||
is_active=True, add_to_group=False, set_setting=False)
|
"admin",
|
||||||
|
"admin@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
is_active=True,
|
||||||
|
add_to_group=False,
|
||||||
|
set_setting=False,
|
||||||
|
)
|
||||||
admin.is_staff = True
|
admin.is_staff = True
|
||||||
admin.is_superuser = True
|
admin.is_superuser = True
|
||||||
admin.save()
|
admin.save()
|
||||||
else:
|
else:
|
||||||
admin = User.objects.get(email='admin@envipath.com')
|
admin = User.objects.get(email="admin@envipath.com")
|
||||||
|
|
||||||
# System Group
|
# 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.public = True
|
||||||
g.save()
|
g.save()
|
||||||
|
|
||||||
@@ -43,14 +60,20 @@ class Command(BaseCommand):
|
|||||||
admin.default_group = g
|
admin.default_group = g
|
||||||
admin.save()
|
admin.save()
|
||||||
|
|
||||||
if not User.objects.filter(email='user0@envipath.com').exists():
|
if not User.objects.filter(email="user0@envipath.com").exists():
|
||||||
user0 = UserManager.create_user("user0", "user0@envipath.com", "SuperSafe",
|
user0 = UserManager.create_user(
|
||||||
is_active=True, add_to_group=False, set_setting=False)
|
"user0",
|
||||||
|
"user0@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
is_active=True,
|
||||||
|
add_to_group=False,
|
||||||
|
set_setting=False,
|
||||||
|
)
|
||||||
user0.is_staff = True
|
user0.is_staff = True
|
||||||
user0.is_superuser = True
|
user0.is_superuser = True
|
||||||
user0.save()
|
user0.save()
|
||||||
else:
|
else:
|
||||||
user0 = User.objects.get(email='user0@envipath.com')
|
user0 = User.objects.get(email="user0@envipath.com")
|
||||||
|
|
||||||
g.user_member.add(user0)
|
g.user_member.add(user0)
|
||||||
g.save()
|
g.save()
|
||||||
@@ -61,18 +84,20 @@ class Command(BaseCommand):
|
|||||||
return anon, admin, g, user0
|
return anon, admin, g, user0
|
||||||
|
|
||||||
def import_package(self, data, owner):
|
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):
|
def create_default_setting(self, owner, packages):
|
||||||
s = SettingManager.create_setting(
|
s = SettingManager.create_setting(
|
||||||
owner,
|
owner,
|
||||||
name='Global Default Setting',
|
name="Global Default Setting",
|
||||||
description='Global Default Setting containing BBD Rules and Max 30 Nodes and Max Depth of 8',
|
description="Global Default Setting containing BBD Rules and Max 30 Nodes and Max Depth of 8",
|
||||||
max_nodes=30,
|
max_nodes=30,
|
||||||
max_depth=5,
|
max_depth=5,
|
||||||
rule_packages=packages,
|
rule_packages=packages,
|
||||||
model=None,
|
model=None,
|
||||||
model_threshold=None
|
model_threshold=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@@ -84,54 +109,51 @@ class Command(BaseCommand):
|
|||||||
"""
|
"""
|
||||||
databases = [
|
databases = [
|
||||||
{
|
{
|
||||||
'name': 'PubChem Compound',
|
"name": "PubChem Compound",
|
||||||
'full_name': 'PubChem Compound Database',
|
"full_name": "PubChem Compound Database",
|
||||||
'description': 'Chemical database of small organic molecules',
|
"description": "Chemical database of small organic molecules",
|
||||||
'base_url': 'https://pubchem.ncbi.nlm.nih.gov',
|
"base_url": "https://pubchem.ncbi.nlm.nih.gov",
|
||||||
'url_pattern': 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'
|
"url_pattern": "https://pubchem.ncbi.nlm.nih.gov/compound/{id}",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'PubChem Substance',
|
"name": "PubChem Substance",
|
||||||
'full_name': 'PubChem Substance Database',
|
"full_name": "PubChem Substance Database",
|
||||||
'description': 'Database of chemical substances',
|
"description": "Database of chemical substances",
|
||||||
'base_url': 'https://pubchem.ncbi.nlm.nih.gov',
|
"base_url": "https://pubchem.ncbi.nlm.nih.gov",
|
||||||
'url_pattern': 'https://pubchem.ncbi.nlm.nih.gov/substance/{id}'
|
"url_pattern": "https://pubchem.ncbi.nlm.nih.gov/substance/{id}",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'ChEBI',
|
"name": "ChEBI",
|
||||||
'full_name': 'Chemical Entities of Biological Interest',
|
"full_name": "Chemical Entities of Biological Interest",
|
||||||
'description': 'Dictionary of molecular entities',
|
"description": "Dictionary of molecular entities",
|
||||||
'base_url': 'https://www.ebi.ac.uk/chebi',
|
"base_url": "https://www.ebi.ac.uk/chebi",
|
||||||
'url_pattern': 'https://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:{id}'
|
"url_pattern": "https://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:{id}",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'RHEA',
|
"name": "RHEA",
|
||||||
'full_name': 'RHEA Reaction Database',
|
"full_name": "RHEA Reaction Database",
|
||||||
'description': 'Comprehensive resource of biochemical reactions',
|
"description": "Comprehensive resource of biochemical reactions",
|
||||||
'base_url': 'https://www.rhea-db.org',
|
"base_url": "https://www.rhea-db.org",
|
||||||
'url_pattern': 'https://www.rhea-db.org/rhea/{id}'
|
"url_pattern": "https://www.rhea-db.org/rhea/{id}",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'KEGG Reaction',
|
"name": "KEGG Reaction",
|
||||||
'full_name': 'KEGG Reaction Database',
|
"full_name": "KEGG Reaction Database",
|
||||||
'description': 'Database of biochemical reactions',
|
"description": "Database of biochemical reactions",
|
||||||
'base_url': 'https://www.genome.jp',
|
"base_url": "https://www.genome.jp",
|
||||||
'url_pattern': 'https://www.genome.jp/entry/reaction+{id}'
|
"url_pattern": "https://www.genome.jp/entry/{id}",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'UniProt',
|
"name": "UniProt",
|
||||||
'full_name': 'MetaCyc Metabolic Pathway Database',
|
"full_name": "MetaCyc Metabolic Pathway Database",
|
||||||
'description': 'UniProt is a freely accessible database of protein sequence and functional information',
|
"description": "UniProt is a freely accessible database of protein sequence and functional information",
|
||||||
'base_url': 'https://www.uniprot.org',
|
"base_url": "https://www.uniprot.org",
|
||||||
'url_pattern': 'https://www.uniprot.org/uniprotkb?query="{id}"'
|
"url_pattern": 'https://www.uniprot.org/uniprotkb?query="{id}"',
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
for db_info in databases:
|
for db_info in databases:
|
||||||
ExternalDatabase.objects.get_or_create(
|
ExternalDatabase.objects.get_or_create(name=db_info["name"], defaults=db_info)
|
||||||
name=db_info['name'],
|
|
||||||
defaults=db_info
|
|
||||||
)
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
@@ -142,20 +164,24 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
# Import Packages
|
# Import Packages
|
||||||
packages = [
|
packages = [
|
||||||
'EAWAG-BBD.json',
|
"EAWAG-BBD.json",
|
||||||
'EAWAG-SOIL.json',
|
"EAWAG-SOIL.json",
|
||||||
'EAWAG-SLUDGE.json',
|
"EAWAG-SLUDGE.json",
|
||||||
'EAWAG-SEDIMENT.json',
|
"EAWAG-SEDIMENT.json",
|
||||||
]
|
]
|
||||||
|
|
||||||
mapping = {}
|
mapping = {}
|
||||||
for p in packages:
|
for p in packages:
|
||||||
print(f"Importing {p}...")
|
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)
|
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.public = True
|
||||||
setting.save()
|
setting.save()
|
||||||
setting.make_global_default()
|
setting.make_global_default()
|
||||||
@@ -171,26 +197,28 @@ class Command(BaseCommand):
|
|||||||
usp.save()
|
usp.save()
|
||||||
|
|
||||||
# Create Model Package
|
# Create Model Package
|
||||||
pack = PackageManager.create_package(admin, "Public Prediction Models",
|
pack = PackageManager.create_package(
|
||||||
"Package to make Prediction Models publicly available")
|
admin,
|
||||||
|
"Public Prediction Models",
|
||||||
|
"Package to make Prediction Models publicly available",
|
||||||
|
)
|
||||||
pack.reviewed = True
|
pack.reviewed = True
|
||||||
pack.save()
|
pack.save()
|
||||||
|
|
||||||
# Create RR
|
# Create RR
|
||||||
ml_model = MLRelativeReasoning.create(
|
ml_model = MLRelativeReasoning.create(
|
||||||
package=pack,
|
package=pack,
|
||||||
rule_packages=[mapping['EAWAG-BBD']],
|
rule_packages=[mapping["EAWAG-BBD"]],
|
||||||
data_packages=[mapping['EAWAG-BBD']],
|
data_packages=[mapping["EAWAG-BBD"]],
|
||||||
eval_packages=[],
|
eval_packages=[],
|
||||||
threshold=0.5,
|
threshold=0.5,
|
||||||
name='ECC - BBD - T0.5',
|
name="ECC - BBD - T0.5",
|
||||||
description='ML Relative Reasoning',
|
description="ML Relative Reasoning",
|
||||||
)
|
)
|
||||||
|
|
||||||
ml_model.build_dataset()
|
ml_model.build_dataset()
|
||||||
ml_model.build_model()
|
ml_model.build_model()
|
||||||
# ml_model.evaluate_model()
|
|
||||||
|
|
||||||
# If available, create EnviFormerModel
|
# If available, create EnviFormerModel
|
||||||
if s.ENVIFORMER_PRESENT:
|
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)
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
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] OPTIONAL: -e [eval_packages]`
|
||||||
|
For example, to train both EnviFormer and MLRelativeReasoning on BBD and SOIL and evaluate them on SLUDGE
|
||||||
|
the below command would be used:
|
||||||
|
`python manage.py create_ml_models enviformer mlrr -d bbd soil -e sludge
|
||||||
|
"""
|
||||||
|
|
||||||
|
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=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
@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']}")
|
||||||
|
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=0.5,
|
||||||
|
name="EnviFormer - T0.5",
|
||||||
|
description="EnviFormer transformer",
|
||||||
|
)
|
||||||
|
elif model_name == "mlrr":
|
||||||
|
model = MLRelativeReasoning.create(
|
||||||
|
package=pack,
|
||||||
|
rule_packages=rule_packages,
|
||||||
|
data_packages=data_packages,
|
||||||
|
eval_packages=eval_packages,
|
||||||
|
threshold=0.5,
|
||||||
|
name="ECC - BBD - T0.5",
|
||||||
|
description="ML Relative Reasoning",
|
||||||
|
)
|
||||||
|
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()
|
||||||
|
print(f"Saving {model_name}")
|
||||||
|
model.save()
|
||||||
@@ -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.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
from epdb.logic import PackageManager
|
from epdb.logic import PackageManager
|
||||||
from epdb.models import *
|
from epdb.models import User
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--data',
|
"--data",
|
||||||
type=str,
|
type=str,
|
||||||
help='Path of the Package to import.',
|
help="Path of the Package to import.",
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--owner',
|
"--owner",
|
||||||
type=str,
|
type=str,
|
||||||
help='Username of the desired Owner.',
|
help="Username of the desired Owner.",
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
owner = User.objects.get(username=options['owner'])
|
owner = User.objects.get(username=options["owner"])
|
||||||
package_data = json.load(open(options['data']))
|
package_data = json.load(open(options["data"]))
|
||||||
PackageManager.import_legacy_package(package_data, owner)
|
PackageManager.import_legacy_package(package_data, owner)
|
||||||
|
|||||||
@@ -1,51 +1,64 @@
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from django.db.models import F, Value
|
from django.db.models import F, Value, TextField, JSONField
|
||||||
from django.db.models.functions import Replace
|
from django.db.models.functions import Replace, Cast
|
||||||
|
|
||||||
|
from epdb.models import EnviPathModel
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--old',
|
"--old",
|
||||||
type=str,
|
type=str,
|
||||||
help='Old Host, most likely https://envipath.org/',
|
help="Old Host, most likely https://envipath.org/",
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--new',
|
"--new",
|
||||||
type=str,
|
type=str,
|
||||||
help='New Host, most likely http://localhost:8000/',
|
help="New Host, most likely http://localhost:8000/",
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
MODELS = [
|
MODELS = [
|
||||||
'User',
|
"User",
|
||||||
'Group',
|
"Group",
|
||||||
'Package',
|
"Package",
|
||||||
'Compound',
|
"Compound",
|
||||||
'CompoundStructure',
|
"CompoundStructure",
|
||||||
'Pathway',
|
"Pathway",
|
||||||
'Edge',
|
"Edge",
|
||||||
'Node',
|
"Node",
|
||||||
'Reaction',
|
"Reaction",
|
||||||
'SimpleAmbitRule',
|
"SimpleAmbitRule",
|
||||||
'SimpleRDKitRule',
|
"SimpleRDKitRule",
|
||||||
'ParallelRule',
|
"ParallelRule",
|
||||||
'SequentialRule',
|
"SequentialRule",
|
||||||
'Scenario',
|
"Scenario",
|
||||||
'Setting',
|
"Setting",
|
||||||
'MLRelativeReasoning',
|
"MLRelativeReasoning",
|
||||||
'RuleBasedRelativeReasoning',
|
"RuleBasedRelativeReasoning",
|
||||||
'EnviFormer',
|
"EnviFormer",
|
||||||
'ApplicabilityDomain',
|
"ApplicabilityDomain",
|
||||||
|
"EnzymeLink",
|
||||||
]
|
]
|
||||||
for model in MODELS:
|
for model in MODELS:
|
||||||
obj_cls = apps.get_model("epdb", model)
|
obj_cls = apps.get_model("epdb", model)
|
||||||
print(f"Localizing urls for {model}")
|
print(f"Localizing urls for {model}")
|
||||||
obj_cls.objects.update(
|
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(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,22 +3,25 @@ from django.shortcuts import redirect
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
|
||||||
class LoginRequiredMiddleware:
|
class LoginRequiredMiddleware:
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
self.exempt_urls = [
|
self.exempt_urls = [
|
||||||
reverse('login'),
|
reverse("login"),
|
||||||
reverse('logout'),
|
reverse("logout"),
|
||||||
reverse('admin:login'),
|
reverse("admin:login"),
|
||||||
reverse('admin:index'),
|
reverse("admin:index"),
|
||||||
] + getattr(settings, 'LOGIN_EXEMPT_URLS', [])
|
] + getattr(settings, "LOGIN_EXEMPT_URLS", [])
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
path = request.path_info
|
path = request.path_info
|
||||||
if not any(path.startswith(url) for url in self.exempt_urls):
|
if not any(path.startswith(url) for url in self.exempt_urls):
|
||||||
if request.method == 'GET':
|
if request.method == "GET":
|
||||||
if request.get_full_path() and request.get_full_path() != '/':
|
if request.get_full_path() and request.get_full_path() != "/":
|
||||||
return redirect(f"{settings.LOGIN_URL}?next={quote(request.get_full_path())}")
|
return redirect(
|
||||||
|
f"{settings.LOGIN_URL}?next={quote(request.get_full_path())}"
|
||||||
|
)
|
||||||
return redirect(settings.LOGIN_URL)
|
return redirect(settings.LOGIN_URL)
|
||||||
return self.get_response(request)
|
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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
+1473
-722
File diff suppressed because it is too large
Load Diff
+15
-15
@@ -2,55 +2,57 @@ import logging
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from epdb.models import Pathway, Node, Edge, EPModel, Setting
|
from epdb.models import Pathway, Node, EPModel, Setting
|
||||||
from epdb.logic import SPathway
|
from epdb.logic import SPathway
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue='background')
|
@shared_task(queue="background")
|
||||||
def mul(a, b):
|
def mul(a, b):
|
||||||
return a * b
|
return a * b
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue='predict')
|
@shared_task(queue="predict")
|
||||||
def predict_simple(model_pk: int, smiles: str):
|
def predict_simple(model_pk: int, smiles: str):
|
||||||
mod = EPModel.objects.get(id=model_pk)
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
res = mod.predict(smiles)
|
res = mod.predict(smiles)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue='background')
|
@shared_task(queue="background")
|
||||||
def send_registration_mail(user_pk: int):
|
def send_registration_mail(user_pk: int):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue='model')
|
@shared_task(queue="model")
|
||||||
def build_model(model_pk: int):
|
def build_model(model_pk: int):
|
||||||
mod = EPModel.objects.get(id=model_pk)
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
mod.build_dataset()
|
mod.build_dataset()
|
||||||
mod.build_model()
|
mod.build_model()
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue='model')
|
@shared_task(queue="model")
|
||||||
def evaluate_model(model_pk: int):
|
def evaluate_model(model_pk: int):
|
||||||
mod = EPModel.objects.get(id=model_pk)
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
mod.evaluate_model()
|
mod.evaluate_model()
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue='model')
|
@shared_task(queue="model")
|
||||||
def retrain(model_pk: int):
|
def retrain(model_pk: int):
|
||||||
mod = EPModel.objects.get(id=model_pk)
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
mod.retrain()
|
mod.retrain()
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue='predict')
|
@shared_task(queue="predict")
|
||||||
def predict(pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_pk: Optional[int] = None) -> Pathway:
|
def predict(
|
||||||
|
pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_pk: Optional[int] = None
|
||||||
|
) -> Pathway:
|
||||||
pw = Pathway.objects.get(id=pw_pk)
|
pw = Pathway.objects.get(id=pw_pk)
|
||||||
setting = Setting.objects.get(id=pred_setting_pk)
|
setting = Setting.objects.get(id=pred_setting_pk)
|
||||||
|
|
||||||
pw.kv.update(**{'status': 'running'})
|
pw.kv.update(**{"status": "running"})
|
||||||
pw.save()
|
pw.save()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -74,12 +76,10 @@ def predict(pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Neither limit nor node_pk given!")
|
raise ValueError("Neither limit nor node_pk given!")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pw.kv.update({'status': 'failed'})
|
pw.kv.update({"status": "failed"})
|
||||||
pw.save()
|
pw.save()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
pw.kv.update(**{'status': 'completed'})
|
pw.kv.update(**{"status": "completed"})
|
||||||
pw.save()
|
pw.save()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from django import template
|
|||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def classname(obj):
|
def classname(obj):
|
||||||
return obj.__class__.__name__
|
return obj.__class__.__name__
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
+171
-75
@@ -1,99 +1,195 @@
|
|||||||
from django.urls import path, re_path
|
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.urls import path, re_path
|
||||||
|
|
||||||
from . import views as v
|
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 = [
|
urlpatterns = [
|
||||||
# Home
|
# Home
|
||||||
re_path(r'^$', v.index, name='index'),
|
re_path(r"^$", v.index, name="index"),
|
||||||
|
|
||||||
# Login
|
# Login
|
||||||
re_path(r'^login', v.login, name='login'),
|
re_path(r"^login", v.login, name="login"),
|
||||||
re_path(r'^logout', v.logout, name='logout'),
|
re_path(r"^logout", v.logout, name="logout"),
|
||||||
re_path(r'^register', v.register, name='register'),
|
re_path(r"^register", v.register, name="register"),
|
||||||
|
# Built-In views
|
||||||
# Built In views
|
path(
|
||||||
path('password_reset/', auth_views.PasswordResetView.as_view(
|
"password_reset/",
|
||||||
template_name='static/password_reset_form.html'
|
auth_views.PasswordResetView.as_view(template_name="static/password_reset_form.html"),
|
||||||
), name='password_reset'),
|
name="password_reset",
|
||||||
|
),
|
||||||
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(
|
path(
|
||||||
template_name='static/password_reset_done.html'
|
"password_reset/done/",
|
||||||
), name='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'
|
path(
|
||||||
), name='password_reset_confirm'),
|
"reset/<uidb64>/<token>/",
|
||||||
|
auth_views.PasswordResetConfirmView.as_view(
|
||||||
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(
|
template_name="static/password_reset_confirm.html"
|
||||||
template_name='static/password_reset_complete.html'
|
),
|
||||||
), name='password_reset_complete'),
|
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
|
# Top level urls
|
||||||
re_path(r'^package$', v.packages, name='packages'),
|
re_path(r"^package$", v.packages, name="packages"),
|
||||||
re_path(r'^compound$', v.compounds, name='compounds'),
|
re_path(r"^compound$", v.compounds, name="compounds"),
|
||||||
re_path(r'^rule$', v.rules, name='rules'),
|
re_path(r"^rule$", v.rules, name="rules"),
|
||||||
re_path(r'^reaction$', v.reactions, name='reactions'),
|
re_path(r"^reaction$", v.reactions, name="reactions"),
|
||||||
re_path(r'^pathway$', v.pathways, name='pathways'),
|
re_path(r"^pathway$", v.pathways, name="pathways"),
|
||||||
re_path(r'^scenario$', v.scenarios, name='scenarios'),
|
re_path(r"^scenario$", v.scenarios, name="scenarios"),
|
||||||
re_path(r'^model$', v.models, name='model'),
|
re_path(r"^model$", v.models, name="model"),
|
||||||
re_path(r'^user$', v.users, name='users'),
|
re_path(r"^user$", v.users, name="users"),
|
||||||
re_path(r'^group$', v.groups, name='groups'),
|
re_path(r"^group$", v.groups, name="groups"),
|
||||||
re_path(r'^search$', v.search, name='search'),
|
re_path(r"^search$", v.search, name="search"),
|
||||||
|
|
||||||
|
|
||||||
# User Detail
|
# 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
|
# 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
|
# "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
|
# Compound
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/compound$', v.package_compounds, name='package compound list'),
|
re_path(
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})$', v.package_compound, name='package compound detail'),
|
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
|
# 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(
|
||||||
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'),
|
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
|
# 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$", 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(
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})$', v.package_rule, name='package rule detail'),
|
rf"^package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})$",
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$', v.package_rule, name='package rule detail'),
|
v.package_rule,
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})$', v.package_rule, name='package rule detail'),
|
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})/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
|
# Reaction
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/reaction$', v.package_reactions, name='package reaction list'),
|
re_path(
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/reaction/(?P<reaction_uuid>{UUID})$', v.package_reaction, name='package reaction detail'),
|
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
|
# # Pathway
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway$', v.package_pathways, name='package pathway list'),
|
re_path(
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})$', v.package_pathway, name='package pathway detail'),
|
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
|
# 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(
|
||||||
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'),
|
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
|
# 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(
|
||||||
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'),
|
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
|
# Scenario
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario$', v.package_scenarios, name='package scenario list'),
|
re_path(
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario/(?P<scenario_uuid>{UUID})$', v.package_scenario, name='package scenario detail'),
|
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
|
# Model
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/model$', v.package_models, name='package model list'),
|
re_path(
|
||||||
re_path(rf'^package/(?P<package_uuid>{UUID})/model/(?P<model_uuid>{UUID})$', v.package_model,name='package model detail'),
|
rf"^package/(?P<package_uuid>{UUID})/model$", v.package_models, name="package model list"
|
||||||
|
),
|
||||||
re_path(r'^setting$', v.settings, name='settings'),
|
re_path(
|
||||||
re_path(rf'^setting/(?P<setting_uuid>{UUID})', v.setting, name='setting'),
|
rf"^package/(?P<package_uuid>{UUID})/model/(?P<model_uuid>{UUID})$",
|
||||||
|
v.package_model,
|
||||||
re_path(r'^indigo/info$', v.indigo, name='indigo_info'),
|
name="package model detail",
|
||||||
re_path(r'^indigo/aromatize$', v.aromatize, name='indigo_aromatize'),
|
),
|
||||||
re_path(r'^indigo/dearomatize$', v.dearomatize, name='indigo_dearomatize'),
|
re_path(r"^setting$", v.settings, name="settings"),
|
||||||
re_path(r'^indigo/layout$', v.layout, name='indigo_layout'),
|
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'^depict$', v.depict, name='depict'),
|
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"),
|
||||||
# OAuth Stuff
|
# OAuth Stuff
|
||||||
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
|
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
|
||||||
]
|
]
|
||||||
|
|||||||
+1422
-973
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
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})/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})/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/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'),
|
||||||
]
|
]
|
||||||
|
|||||||
+189
-111
@@ -1,80 +1,123 @@
|
|||||||
import gzip
|
import gzip
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
|
from django.http import HttpResponseNotAllowed
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
from epdb.logic import PackageManager
|
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 epdb.views import get_base_context, _anonymous_or_real
|
||||||
from utilities.chem import FormatConverter
|
from utilities.chem import FormatConverter
|
||||||
|
|
||||||
|
|
||||||
|
from rdkit import Chem
|
||||||
|
from rdkit.Chem.MolStandardize import rdMolStandardize
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def run_both_engines(SMILES, SMIRKS):
|
||||||
|
from envipy_ambit import apply
|
||||||
|
|
||||||
|
ambit_res = apply(SMIRKS, SMILES)
|
||||||
|
# ambit_res, ambit_errors = FormatConverter.sanitize_smiles([str(s) for s in ambit_res])
|
||||||
|
|
||||||
|
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(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))
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
def migration(request):
|
def migration(request):
|
||||||
if request.method == 'GET':
|
if request.method == "GET":
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
if os.path.exists(s.BASE_DIR / 'fixtures' / 'migration_status_per_rule.json') and request.GET.get(
|
if (
|
||||||
"force") is None:
|
os.path.exists(s.BASE_DIR / "fixtures" / "migration_status_per_rule.json")
|
||||||
migration_status = json.load(open(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:
|
else:
|
||||||
data = json.load(gzip.open(s.BASE_DIR / 'fixtures' / 'ambit_rules.json.gz', 'rb'))
|
BBD = Package.objects.get(
|
||||||
|
url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1"
|
||||||
results = []
|
)
|
||||||
|
ALL_SMILES = [
|
||||||
|
cs.smiles
|
||||||
|
for cs in CompoundStructure.objects.filter(compound__package=BBD)
|
||||||
|
]
|
||||||
|
RULES = SimpleAmbitRule.objects.filter(package=BBD)
|
||||||
|
|
||||||
|
results = list()
|
||||||
|
num_rules = len(RULES)
|
||||||
success = 0
|
success = 0
|
||||||
error = 0
|
error = 0
|
||||||
total = 0
|
total = 0
|
||||||
|
|
||||||
num_keys = len(data.keys())
|
for i, r in enumerate(RULES):
|
||||||
for i, bt_rule_name in enumerate(data.keys()):
|
logger.debug(f"\r{i + 1:03d}/{num_rules}")
|
||||||
print(f"{i + 1}/{num_keys}")
|
|
||||||
bt_rule = data[bt_rule_name]
|
|
||||||
smirks = bt_rule['smirks']
|
|
||||||
|
|
||||||
all_prods = set()
|
|
||||||
|
|
||||||
res = True
|
res = True
|
||||||
|
for smiles in ALL_SMILES:
|
||||||
|
try:
|
||||||
|
ambit_res, _, rdkit_res, _ = run_both_engines(smiles, r.smirks)
|
||||||
|
|
||||||
for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']):
|
res &= set(ambit_res) == set(rdkit_res)
|
||||||
|
except Exception as e:
|
||||||
products = FormatConverter.apply(comp['smiles'], smirks)
|
logger.error(e)
|
||||||
|
|
||||||
all_rdkit_prods = []
|
|
||||||
for ps in products:
|
|
||||||
for p in ps:
|
|
||||||
all_rdkit_prods.append(p)
|
|
||||||
|
|
||||||
all_rdkit_prods = list(set(all_rdkit_prods))
|
|
||||||
|
|
||||||
ambit_smiles, ambit_errors = FormatConverter.sanitize_smiles(ambit_prod)
|
|
||||||
rdkit_smiles, rdkit_errors = FormatConverter.sanitize_smiles(all_rdkit_prods)
|
|
||||||
|
|
||||||
for x in ambit_smiles:
|
|
||||||
all_prods.add(x)
|
|
||||||
|
|
||||||
# TODO mode "intersection"
|
|
||||||
# partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0)
|
|
||||||
# FAILED (failures=37)
|
|
||||||
|
|
||||||
# TODO mode = "full ambit"
|
|
||||||
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles)
|
|
||||||
# FAILED (failures=46)
|
|
||||||
|
|
||||||
# TODO mode = "equality"
|
|
||||||
partial_res = set(ambit_smiles) == set(rdkit_smiles)
|
|
||||||
# FAILED (failures=69)
|
|
||||||
|
|
||||||
res &= partial_res
|
|
||||||
|
|
||||||
results.append(
|
results.append(
|
||||||
{
|
{
|
||||||
'name': bt_rule_name,
|
"name": r.name,
|
||||||
'id': bt_rule['id'].split('/')[-1],
|
"detail_url": s.SERVER_URL
|
||||||
'url': bt_rule['id'],
|
+ "/migration/"
|
||||||
'status': res,
|
+ r.url.replace("https://envipath.org/", "").replace(
|
||||||
'detail_url': s.SERVER_URL + '/migration/' + bt_rule['id'].replace('https://envipath.org/', '')
|
"http://localhost:8000/", ""
|
||||||
|
),
|
||||||
|
"id": str(r.uuid),
|
||||||
|
"url": r.url,
|
||||||
|
"status": res,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,95 +127,82 @@ def migration(request):
|
|||||||
error += 1
|
error += 1
|
||||||
|
|
||||||
total += 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 = {
|
migration_status = {
|
||||||
'results': results,
|
"results": results,
|
||||||
'success': success,
|
"success": success,
|
||||||
'error': error,
|
"error": error,
|
||||||
'total': total
|
"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']:
|
for r in migration_status["results"]:
|
||||||
r['detail_url'] = r['detail_url'].replace('http://localhost:8000', s.SERVER_URL)
|
r["detail_url"] = r["detail_url"].replace(
|
||||||
|
"http://localhost:8000", s.SERVER_URL
|
||||||
|
)
|
||||||
|
|
||||||
context.update(**migration_status)
|
context.update(**migration_status)
|
||||||
|
|
||||||
return render(request, 'migration.html', context)
|
return render(request, "migration.html", context)
|
||||||
|
|
||||||
|
|
||||||
def migration_detail(request, package_uuid, rule_uuid):
|
def migration_detail(request, package_uuid, rule_uuid):
|
||||||
current_user = _anonymous_or_real(request)
|
current_user = _anonymous_or_real(request)
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == "GET":
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
p = PackageManager.get_package_by_id(current_user, package_uuid)
|
BBD = Package.objects.get(name="EAWAG-BBD")
|
||||||
rule = Rule.objects.get(package=p, uuid=rule_uuid)
|
STRUCTURES = CompoundStructure.objects.filter(compound__package=BBD)
|
||||||
|
rule = Rule.objects.get(package=BBD, uuid=rule_uuid)
|
||||||
|
|
||||||
bt_rule_name = rule.name
|
bt_rule_name = rule.name
|
||||||
|
smirks = rule.smirks
|
||||||
data = json.load(gzip.open(s.BASE_DIR / 'fixtures' / 'ambit_rules.json.gz', 'rb'))
|
|
||||||
|
|
||||||
bt_rule = data[bt_rule_name]
|
|
||||||
smirks = bt_rule['smirks']
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
res = True
|
res = True
|
||||||
|
results = []
|
||||||
|
|
||||||
all_prods = set()
|
all_prods = set()
|
||||||
for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']):
|
for structure in STRUCTURES:
|
||||||
# if comp['smiles'] != 'CC1=C(C(=C(C=N1)CO)C=O)O':
|
ambit_smiles, ambit_errors, rdkit_smiles, rdkit_errors = run_both_engines(
|
||||||
# continue
|
structure.smiles, smirks
|
||||||
|
)
|
||||||
products = FormatConverter.apply(comp['smiles'], smirks)
|
|
||||||
|
|
||||||
all_rdkit_prods = []
|
|
||||||
for ps in products:
|
|
||||||
for p in ps:
|
|
||||||
all_rdkit_prods.append(p)
|
|
||||||
|
|
||||||
all_rdkit_prods = list(set(all_rdkit_prods))
|
|
||||||
|
|
||||||
ambit_smiles, ambit_errors = FormatConverter.sanitize_smiles(ambit_prod)
|
|
||||||
rdkit_smiles, rdkit_errors = FormatConverter.sanitize_smiles(all_rdkit_prods)
|
|
||||||
|
|
||||||
for x in ambit_smiles:
|
for x in ambit_smiles:
|
||||||
all_prods.add(x)
|
all_prods.add(x)
|
||||||
|
|
||||||
# TODO mode "intersection"
|
# TODO mode "intersection"
|
||||||
# partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0)
|
# 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"
|
# TODO mode = "full ambit"
|
||||||
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles)
|
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(set(ambit_smiles))
|
||||||
# FAILED (failures=46)
|
# FAILED (failures=34)
|
||||||
|
|
||||||
# TODO mode = "equality"
|
# TODO mode = "equality"
|
||||||
partial_res = set(ambit_smiles) == set(rdkit_smiles)
|
partial_res = set(ambit_smiles) == set(rdkit_smiles)
|
||||||
# FAILED (failures=69)
|
# FAILED (failures=30)
|
||||||
|
|
||||||
#
|
|
||||||
if len(ambit_smiles) or len(rdkit_smiles):
|
if len(ambit_smiles) or len(rdkit_smiles):
|
||||||
temp = {
|
temp = {
|
||||||
'url': comp['id'],
|
"url": structure.url,
|
||||||
'id': comp['id'].split('/')[-1],
|
"id": str(structure.uuid),
|
||||||
'name': comp['name'],
|
"name": structure.name,
|
||||||
'initial_smiles': comp['smiles'],
|
"initial_smiles": structure.smiles,
|
||||||
'ambit_smiles': sorted(list(ambit_smiles)),
|
"ambit_smiles": sorted(list(ambit_smiles)),
|
||||||
'rdkit_smiles': sorted(list(rdkit_smiles)),
|
"rdkit_smiles": sorted(list(rdkit_smiles)),
|
||||||
'status': set(ambit_smiles) == set(rdkit_smiles),
|
"status": set(ambit_smiles) == set(rdkit_smiles),
|
||||||
}
|
}
|
||||||
|
detail = f"""
|
||||||
if set(ambit_smiles) != set(rdkit_smiles):
|
|
||||||
detail = f"""
|
|
||||||
BT: {bt_rule_name}
|
BT: {bt_rule_name}
|
||||||
SMIRKS: {bt_rule['smirks']}
|
SMIRKS: {smirks}
|
||||||
Compound: {comp['smiles']}
|
Compound: {structure.smiles}
|
||||||
Compound URL: {comp['id']}
|
Compound URL: {structure.url}
|
||||||
Num ambit: {len(set(ambit_smiles))}
|
Num ambit: {len(set(ambit_smiles))}
|
||||||
Num rdkit: {len(set(rdkit_smiles))}
|
Num rdkit: {len(set(rdkit_smiles))}
|
||||||
Num Intersection A: {len(set(ambit_smiles).intersection(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}
|
rdkit_errors: {rdkit_errors}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
temp['detail'] = '\n'.join([x.strip() for x in detail.split('\n')])
|
temp["detail"] = "\n".join([x.strip() for x in detail.split("\n")])
|
||||||
# print(detail.strip())
|
|
||||||
|
|
||||||
results.append(temp)
|
results.append(temp)
|
||||||
|
|
||||||
res &= partial_res
|
res &= partial_res
|
||||||
|
|
||||||
results = sorted(results, key=lambda x: x['status'])
|
results = sorted(results, key=lambda x: x["status"])
|
||||||
context['results'] = results
|
context["results"] = results
|
||||||
context['res'] = res
|
context["res"] = res
|
||||||
context['bt_rule_name'] = bt_rule_name
|
context["bt_rule_name"] = bt_rule_name
|
||||||
return render(request, 'migration_detail.html', context)
|
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"])
|
||||||
|
|||||||
+57
-4
@@ -3,7 +3,7 @@ name = "envipy"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"celery>=5.5.2",
|
"celery>=5.5.2",
|
||||||
"django>=5.2.1",
|
"django>=5.2.1",
|
||||||
@@ -12,9 +12,9 @@ dependencies = [
|
|||||||
"django-ninja>=1.4.1",
|
"django-ninja>=1.4.1",
|
||||||
"django-oauth-toolkit>=3.0.1",
|
"django-oauth-toolkit>=3.0.1",
|
||||||
"django-polymorphic>=4.1.0",
|
"django-polymorphic>=4.1.0",
|
||||||
"django-stubs>=5.2.4",
|
|
||||||
"enviformer",
|
"enviformer",
|
||||||
"envipy-additional-information",
|
"envipy-additional-information",
|
||||||
|
"envipy-ambit>=0.1.0",
|
||||||
"envipy-plugins",
|
"envipy-plugins",
|
||||||
"epam-indigo>=1.30.1",
|
"epam-indigo>=1.30.1",
|
||||||
"gunicorn>=23.0.0",
|
"gunicorn>=23.0.0",
|
||||||
@@ -30,9 +30,62 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv.sources]
|
[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-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]
|
[project.optional-dependencies]
|
||||||
ms-login = ["msal>=1.33.0"]
|
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" }
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -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 |
+4
-4
@@ -646,8 +646,8 @@ function handleAssessmentResponse(depict_url, data) {
|
|||||||
var reactivityCentersImgSrc = null;
|
var reactivityCentersImgSrc = null;
|
||||||
|
|
||||||
if (data['assessment']['node'] !== undefined) {
|
if (data['assessment']['node'] !== undefined) {
|
||||||
functionalGroupsImgSrc = "<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'] + "'>"
|
reactivityCentersImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "&highlightReactivity=true'>"
|
||||||
} else {
|
} else {
|
||||||
functionalGroupsImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">";
|
functionalGroupsImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">";
|
||||||
reactivityCentersImgSrc = "<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 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 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 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']) + "'>";
|
var transImg = "<img width='100%' src='" + transObj['rule']['url'] + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "'>";
|
||||||
|
|
||||||
@@ -784,4 +784,4 @@ function handleAssessmentResponse(depict_url, data) {
|
|||||||
|
|
||||||
$("#appDomainAssessmentResultTable").append(res);
|
$("#appDomainAssessmentResultTable").append(res);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -444,6 +444,13 @@ function serializeSVG(svgElement) {
|
|||||||
line.setAttribute("fill", style.fill);
|
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();
|
const serializer = new XMLSerializer();
|
||||||
let svgString = serializer.serializeToString(svgElement);
|
let svgString = serializer.serializeToString(svgElement);
|
||||||
|
|
||||||
@@ -455,7 +462,26 @@ function serializeSVG(svgElement) {
|
|||||||
return svgString;
|
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') {
|
function downloadSVG(svgElement, filename = 'chart.svg') {
|
||||||
|
shrinkSVG("#" + svgElement.id);
|
||||||
const svgString = serializeSVG(svgElement);
|
const svgString = serializeSVG(svgElement);
|
||||||
const blob = new Blob([svgString], {type: 'image/svg+xml;charset=utf-8'});
|
const blob = new Blob([svgString], {type: 'image/svg+xml;charset=utf-8'});
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
<a role="button" data-toggle="modal" data-target="#edit_compound_modal">
|
<a role="button" data-toggle="modal" data-target="#edit_compound_modal">
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a>
|
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#add_structure_modal">
|
<a role="button" data-toggle="modal" data-target="#add_structure_modal">
|
||||||
<i class="glyphicon glyphicon-plus"></i> Add Structure</a>
|
<i class="glyphicon glyphicon-plus"></i> Add Structure</a>
|
||||||
@@ -11,6 +15,10 @@
|
|||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||||
</li>
|
</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 %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
||||||
@@ -21,4 +29,4 @@
|
|||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -3,12 +3,20 @@
|
|||||||
<a role="button" data-toggle="modal" data-target="#edit_compound_structure_modal">
|
<a role="button" data-toggle="modal" data-target="#edit_compound_structure_modal">
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a>
|
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||||
</li>
|
</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>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a>
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
{% if meta.can_edit %}
|
{% 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>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
<a role="button" data-toggle="modal" data-target="#edit_node_modal">
|
<a role="button" data-toggle="modal" data-target="#edit_node_modal">
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Node</a>
|
<i class="glyphicon glyphicon-edit"></i> Edit Node</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||||
|
|||||||
@@ -31,6 +31,10 @@
|
|||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||||
|
</li>
|
||||||
{# <li>#}
|
{# <li>#}
|
||||||
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
|
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
|
||||||
{# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#}
|
{# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#}
|
||||||
@@ -48,4 +52,4 @@
|
|||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a>
|
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -3,10 +3,18 @@
|
|||||||
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
|
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a>
|
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||||
</li>
|
</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 %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
||||||
@@ -17,4 +25,4 @@
|
|||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
|
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
<a role="button" data-toggle="modal" data-target="#edit_rule_modal">
|
<a role="button" data-toggle="modal" data-target="#edit_rule_modal">
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a>
|
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||||
@@ -17,4 +21,4 @@
|
|||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a>
|
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -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>
|
<h4 class="alert-heading">{{ error_message }}</h4>
|
||||||
<hr>
|
<hr>
|
||||||
<p class="mb-0">
|
<p class="mb-0">
|
||||||
{{ error_detail }}<br>
|
{{ error_detail }}
|
||||||
The error was logged and will be investigated.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+14
-12
@@ -89,7 +89,7 @@
|
|||||||
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse">
|
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse">
|
||||||
<ul class="nav navbar-nav navbar-nav-framework">
|
<ul class="nav navbar-nav navbar-nav-framework">
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#predict_modal">
|
<a href="#" data-toggle="modal" data-target="#predict_modal">
|
||||||
Predict Pathway
|
Predict Pathway
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a>
|
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a>
|
||||||
<ul role="menu" class="dropdown-menu">
|
<ul role="menu" class="dropdown-menu">
|
||||||
<!--<li><a href="{{ meta.server_url }}/funding" id="fundingLink">Funding</a></li>-->
|
<!--<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 class="divider"></li>
|
||||||
<li><a target="_blank" href="https://wiki.envipath.org/" id="wikiLink">Documentation Wiki</a>
|
<li><a target="_blank" href="https://wiki.envipath.org/" id="wikiLink">Documentation Wiki</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -227,21 +227,23 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<ul class="nav nav-pills nav-justified">
|
<ul class="nav nav-pills nav-justified">
|
||||||
<li><a href="http://eawag.ch" target="_blank">
|
|
||||||
<img id="image-ealogo"
|
|
||||||
height="60"
|
|
||||||
src='{% static "/images/ealogo.gif" %}'
|
|
||||||
alt="Eawag"/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href="http://ml.auckland.ac.nz" target="_blank">
|
<a href="http://ml.auckland.ac.nz" target="_blank">
|
||||||
<img id="image-uoalogo"
|
<img id="image-uoalogo" height="60" src='{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}'
|
||||||
height="60"
|
|
||||||
src='{% static "/images/uoa.png" %}'
|
|
||||||
alt="The Univserity of Auckland"/>
|
alt="The Univserity of Auckland"/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -103,6 +103,7 @@
|
|||||||
var textSmiles = $('#index-form-text-input').val().trim();
|
var textSmiles = $('#index-form-text-input').val().trim();
|
||||||
|
|
||||||
if (textSmiles === '') {
|
if (textSmiles === '') {
|
||||||
|
$(this).prop("disabled", false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +1,45 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="panel-group" id="migration-detail">
|
<div class="panel-group" id="migration-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
Migration Status for {{ bt_rule_name }}
|
Migration Status for {{ bt_rule_name }}
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<p>A package contains pathways, rules, etc. and can reflect specific experimental
|
|
||||||
conditions. <a target="_blank" href="https://wiki.envipath.org/index.php/packages" role="button">Learn
|
|
||||||
more >></a></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% for obj in results %}
|
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
|
||||||
{% if obj.status %}
|
|
||||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"
|
|
||||||
style="float:right" data-toggle="tooltip"
|
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
|
||||||
</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"
|
|
||||||
style="float:right" data-toggle="tooltip"
|
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
|
||||||
href="#{{ obj.id }}">{{ obj.name }}</a>
|
|
||||||
</h4>
|
|
||||||
</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>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>A package contains pathways, rules, etc. and can reflect specific experimental
|
||||||
|
conditions. <a target="_blank" href="https://wiki.envipath.org/index.php/packages" role="button">Learn
|
||||||
|
more >></a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for obj in results %}
|
||||||
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
|
{% if obj.status %}
|
||||||
|
<span class="glyphicon glyphicon-ok" aria-hidden="true"
|
||||||
|
style="float:right" data-toggle="tooltip"
|
||||||
|
data-placement="top" title="" data-original-title="Reviewed">
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="glyphicon glyphicon-remove" aria-hidden="true"
|
||||||
|
style="float:right" data-toggle="tooltip"
|
||||||
|
data-placement="top" title="" data-original-title="Reviewed">
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
||||||
|
href="#{{ obj.id }}">{{ obj.name }}</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
||||||
|
<div class="panel-body list-group-item">
|
||||||
|
<pre>{{ obj.detail }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
enctype="multipart/form-data">
|
enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<label class="btn btn-primary" for="jsonFile">
|
<label class="btn btn-primary" for="legacyJsonFile">
|
||||||
<input id="jsonFile" name="file" type="file" style="display:none;"
|
<input id="legacyJsonFile" name="file" type="file" style="display:none;"
|
||||||
onchange="$('#upload-file-info').html(this.files[0].name)">
|
onchange="$('#upload-legacy-file-info').html(this.files[0].name)">
|
||||||
Choose JSON File
|
Choose JSON File
|
||||||
</label>
|
</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="">
|
<input type="hidden" value="import-legacy-package-json" name="hidden" readonly="">
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -29,11 +29,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
<input type="number" id="dateYear" name="scenario-date-year" class="form-control"
|
<input type="number" id="dateYear" name="scenario-date-year" class="form-control"
|
||||||
placeholder="YYYY">
|
placeholder="YYYY" max="{% now "Y" %}">
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<input type="number" id="dateMonth" name="scenario-date-month" min="1" max="12"
|
<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>
|
||||||
<th>
|
<th>
|
||||||
<input type="number" id="dateDay" name="scenario-date-day" min="1" max="31" class="form-control"
|
<input type="number" id="dateDay" name="scenario-date-day" min="1" max="31" class="form-control"
|
||||||
@@ -88,8 +88,15 @@
|
|||||||
$('#new_scenario_form').submit();
|
$('#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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<label for="compound-structure-name">Name</label>
|
<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>
|
||||||
<p>
|
<p>
|
||||||
<label for="compound-structure-description">Description</label>
|
<label for="compound-structure-description">Description</label>
|
||||||
<input id="compound-structure-description" type="text" class="form-control"
|
<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>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
</select>
|
</select>
|
||||||
<input type="hidden" name="hidden" value="copy">
|
<input type="hidden" name="hidden" value="copy">
|
||||||
</form>
|
</form>
|
||||||
|
<div id="copy-object-error-message" class="alert alert-danger" role="alert" style="display: none">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
<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) {
|
$('#generic-copy-object-modal-form-submit').click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
$('#copy-object-error-message').hide()
|
||||||
|
|
||||||
const packageUrl = $('#target-package').find(":selected").val();
|
const packageUrl = $('#target-package').find(":selected").val();
|
||||||
|
|
||||||
@@ -49,12 +52,22 @@
|
|||||||
object_to_copy: '{{ current_object.url }}',
|
object_to_copy: '{{ current_object.url }}',
|
||||||
}
|
}
|
||||||
|
|
||||||
$.post(packageUrl, formData, function (response) {
|
$.ajax({
|
||||||
if (response.success) {
|
type: 'post',
|
||||||
window.location.href = response.success;
|
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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"
|
<select id="scenario-select" name="selected-scenarios" data-actions-box='true' class="form-control"
|
||||||
multiple data-width='100%'>
|
multiple data-width='100%'>
|
||||||
<option disabled>Select Scenarios</option>
|
<option disabled>Select Scenarios</option>
|
||||||
|
<option value="" hidden></option>
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,6 +65,9 @@
|
|||||||
|
|
||||||
$('#set_scenario_modal_form_submit').on('click', function (e) {
|
$('#set_scenario_modal_form_submit').on('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if ($('#scenario-select').val().length == 0) {
|
||||||
|
$('#scenario-select').val("")
|
||||||
|
}
|
||||||
$('#set_scenario_modal_form').submit();
|
$('#set_scenario_modal_form').submit();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
{% include "modals/objects/edit_rule_modal.html" %}
|
{% include "modals/objects/edit_rule_modal.html" %}
|
||||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||||
{% include "modals/objects/generic_delete_modal.html" %}
|
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||||
{% endblock action_modals %}
|
{% include "modals/objects/generic_delete_modal.html" %}
|
||||||
|
{% endblock action_modals %}
|
||||||
|
|
||||||
<div class="panel-group" id="rule-detail">
|
<div class="panel-group" id="rule-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@@ -28,10 +29,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p>
|
<p>
|
||||||
{{ rule.description }}
|
{{ rule.description|safe }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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 -->
|
<!-- Reaction Patterns -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
@@ -69,19 +87,41 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- EC Numbers -->
|
{% if rule.enzymelinks %}
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<!-- EC Numbers -->
|
||||||
<h4 class="panel-title">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<a id="rule-ec-numbers-link" data-toggle="collapse" data-parent="#rule-detail"
|
<h4 class="panel-title">
|
||||||
href="#rule-ec-numbers">EC Numbers</a>
|
<a id="rule-ec-numbers-link" data-toggle="collapse" data-parent="#rule-detail"
|
||||||
</h4>
|
href="#rule-ec-numbers">EC Numbers</a>
|
||||||
</div>
|
</h4>
|
||||||
<div id="rule-ec-numbers" class="panel-collapse collapse">
|
|
||||||
<div class="panel-body list-group-item">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
{% include "modals/objects/edit_compound_modal.html" %}
|
{% 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/add_structure_modal.html" %}
|
||||||
{% include "modals/objects/generic_set_scenario_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_copy_object_modal.html" %}
|
||||||
{% include "modals/objects/generic_delete_modal.html" %}
|
{% include "modals/objects/generic_delete_modal.html" %}
|
||||||
{% endblock action_modals %}
|
{% endblock action_modals %}
|
||||||
@@ -36,6 +38,23 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</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 -->
|
<!-- Description -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
{% include "modals/objects/edit_compound_structure_modal.html" %}
|
{% include "modals/objects/edit_compound_structure_modal.html" %}
|
||||||
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||||
{% include "modals/objects/generic_delete_modal.html" %}
|
{% include "modals/objects/generic_set_scenario_modal.html" %}
|
||||||
{% endblock action_modals %}
|
{% 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-group" id="compound-structure-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@@ -32,34 +34,52 @@
|
|||||||
<!-- Image -->
|
<!-- Image -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="compound-image-link" data-toggle="collapse" data-parent="#compound-detail"
|
<a id="compound-structure-image-link" data-toggle="collapse" data-parent="#compound-structure-detail"
|
||||||
href="#compound-image">Image Representation</a>
|
href="#compound-structure-image">Image Representation</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</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 class="panel-body list-group-item">
|
||||||
<div id="image-div" align="center">
|
<div id="image-div" align="center">
|
||||||
{{ compound_structure.as_svg|safe }}
|
{{ compound_structure.as_svg|safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SMILES -->
|
<!-- SMILES -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="compound-smiles-link" data-toggle="collapse" data-parent="#compound-detail"
|
<a id="compound-structure-smiles-link" data-toggle="collapse" data-parent="#compound-structure-detail"
|
||||||
href="#compound-smiles">SMILES Representation</a>
|
href="#compound-structure-smiles">SMILES Representation</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</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">
|
<div class="panel-body list-group-item">
|
||||||
{{ compound_structure.smiles }}
|
{{ compound_structure.smiles }}
|
||||||
</div>
|
</div>
|
||||||
</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 %}
|
{% if compound_structure.scenarios.all %}
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="compound_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>
|
href="#compound-structure-scenario">Scenarios</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
{# {% include "modals/objects/edit_edge_modal.html" %}#}
|
{# {% 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_set_scenario_modal.html" %}
|
||||||
{% include "modals/objects/generic_delete_modal.html" %}
|
{% include "modals/objects/generic_delete_modal.html" %}
|
||||||
{% endblock action_modals %}
|
{% endblock action_modals %}
|
||||||
@@ -39,6 +40,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Image -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -315,12 +315,18 @@
|
|||||||
$("#predict-button").on("click", function (e) {
|
$("#predict-button").on("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
clear("predictResultTable");
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"smiles": $("#smiles-to-predict").val(),
|
"smiles": $("#smiles-to-predict").val(),
|
||||||
"classify": "ILikeCats!"
|
"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' %}");
|
makeLoadingGif("#predictLoading", "{% static '/images/wait.gif' %}");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -332,17 +338,17 @@
|
|||||||
$("#predictLoading").empty();
|
$("#predictLoading").empty();
|
||||||
handlePredictionResponse(data);
|
handlePredictionResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error");
|
|
||||||
$("#predictLoading").empty();
|
$("#predictLoading").empty();
|
||||||
$("#predictResultTable").addClass("alert alert-danger");
|
$("#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();
|
$("#predictLoading").empty();
|
||||||
$("#predictResultTable").addClass("alert alert-danger");
|
$("#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) {
|
$("#assess-button").on("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
clear("appDomainAssessmentResultTable");
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"smiles": $("#smiles-to-assess").val(),
|
"smiles": $("#smiles-to-assess").val(),
|
||||||
"app-domain-assessment": "ILikeCats!"
|
"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' %}");
|
makeLoadingGif("#appDomainLoading", "{% static '/images/wait.gif' %}");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -369,16 +383,15 @@
|
|||||||
handleAssessmentResponse("{% url 'depict' %}", data);
|
handleAssessmentResponse("{% url 'depict' %}", data);
|
||||||
console.log(data);
|
console.log(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error");
|
|
||||||
$("#appDomainLoading").empty();
|
$("#appDomainLoading").empty();
|
||||||
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
||||||
$("#appDomainAssessmentResultTable").append("Error while processing request :/");
|
$("#appDomainAssessmentResultTable").append("Error while processing response :/");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (jqXHR, textStatus, errorThrown) {
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
$("#appDomainLoading").empty();
|
$("#appDomainLoading").empty();
|
||||||
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
||||||
$("#appDomainAssessmentResultTable").append("Error while processing request :/");
|
$("#appDomainAssessmentResultTable").append(jqXHR.responseJSON.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
{% include "modals/objects/edit_node_modal.html" %}
|
{% 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_set_scenario_modal.html" %}
|
||||||
{% include "modals/objects/generic_delete_modal.html" %}
|
{% include "modals/objects/generic_delete_modal.html" %}
|
||||||
{% endblock action_modals %}
|
{% endblock action_modals %}
|
||||||
@@ -42,6 +43,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Image -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p> {{ package.description }} </p>
|
<p> {{ package.description|safe }} </p>
|
||||||
</div>
|
</div>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
{% include "modals/objects/download_pathway_image_modal.html" %}
|
{% include "modals/objects/download_pathway_image_modal.html" %}
|
||||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||||
{% include "modals/objects/edit_pathway_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/generic_set_scenario_modal.html" %}
|
||||||
{% include "modals/objects/delete_pathway_node_modal.html" %}
|
{% include "modals/objects/delete_pathway_node_modal.html" %}
|
||||||
{% include "modals/objects/delete_pathway_edge_modal.html" %}
|
{% include "modals/objects/delete_pathway_edge_modal.html" %}
|
||||||
@@ -176,9 +177,6 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<div id="vizdiv" >
|
<div id="vizdiv" >
|
||||||
<svg id="pwsvg">
|
<svg id="pwsvg">
|
||||||
{% if debug %}
|
|
||||||
<rect width="100%" height="100%" fill="aliceblue"/>
|
|
||||||
{% endif %}
|
|
||||||
<defs>
|
<defs>
|
||||||
<marker id="arrow" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6"
|
<marker id="arrow" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6"
|
||||||
orient="auto-start-reverse" markerUnits="userSpaceOnUse">
|
orient="auto-start-reverse" markerUnits="userSpaceOnUse">
|
||||||
@@ -210,6 +208,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 %}
|
{% if pathway.scenarios.all %}
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
{% include "modals/objects/edit_reaction_modal.html" %}
|
{% 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_set_scenario_modal.html" %}
|
||||||
{% include "modals/objects/generic_copy_object_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" %}
|
{% include "modals/objects/generic_delete_modal.html" %}
|
||||||
{% endblock action_modals %}
|
{% endblock action_modals %}
|
||||||
|
|
||||||
@@ -40,6 +42,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Image -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
@@ -105,6 +124,23 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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 %}
|
{% if reaction.related_pathways %}
|
||||||
<!-- Pathways -->
|
<!-- Pathways -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
{% include "modals/objects/edit_rule_modal.html" %}
|
{% 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_set_scenario_modal.html" %}
|
||||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||||
{% include "modals/objects/generic_delete_modal.html" %}
|
{% include "modals/objects/generic_delete_modal.html" %}
|
||||||
@@ -32,6 +33,23 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</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 -->
|
<!-- Representation -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
@@ -183,6 +201,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -13,91 +13,80 @@ class CompoundTest(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(CompoundTest, cls).setUpClass()
|
super(CompoundTest, cls).setUpClass()
|
||||||
cls.user = User.objects.get(username='anonymous')
|
cls.user = User.objects.get(username="anonymous")
|
||||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||||
|
|
||||||
def test_smoke(self):
|
def test_smoke(self):
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
self.package,
|
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",
|
||||||
name='Afoxolaner',
|
name="Afoxolaner",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(c.default_structure.smiles,
|
self.assertEqual(
|
||||||
'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')
|
c.default_structure.smiles,
|
||||||
self.assertEqual(c.name, 'Afoxolaner')
|
"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.description, 'No Desc')
|
)
|
||||||
|
self.assertEqual(c.name, "Afoxolaner")
|
||||||
|
self.assertEqual(c.description, "No Desc")
|
||||||
|
|
||||||
def test_missing_smiles(self):
|
def test_missing_smiles(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_ = Compound.create(
|
_ = Compound.create(self.package, smiles=None, name="Afoxolaner", description="No Desc")
|
||||||
self.package,
|
|
||||||
smiles=None,
|
|
||||||
name='Afoxolaner',
|
|
||||||
description='No Desc'
|
|
||||||
)
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_ = Compound.create(
|
_ = Compound.create(self.package, smiles="", name="Afoxolaner", description="No Desc")
|
||||||
self.package,
|
|
||||||
smiles='',
|
|
||||||
name='Afoxolaner',
|
|
||||||
description='No Desc'
|
|
||||||
)
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_ = Compound.create(
|
_ = Compound.create(self.package, smiles=" ", name="Afoxolaner", description="No Desc")
|
||||||
self.package,
|
|
||||||
smiles=' ',
|
|
||||||
name='Afoxolaner',
|
|
||||||
description='No Desc'
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_smiles_are_trimmed(self):
|
def test_smiles_are_trimmed(self):
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
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 ",
|
||||||
name='Afoxolaner',
|
name="Afoxolaner",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(c.default_structure.smiles,
|
self.assertEqual(
|
||||||
'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')
|
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):
|
def test_name_and_description_optional(self):
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
self.package,
|
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.name, "Compound 1")
|
||||||
self.assertEqual(c.description, 'no description')
|
self.assertEqual(c.description, "no description")
|
||||||
|
|
||||||
def test_empty_name_and_description_are_ignored(self):
|
def test_empty_name_and_description_are_ignored(self):
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
self.package,
|
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",
|
||||||
name='',
|
name="",
|
||||||
description='',
|
description="",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(c.name, 'Compound 1')
|
self.assertEqual(c.name, "Compound 1")
|
||||||
self.assertEqual(c.description, 'no description')
|
self.assertEqual(c.description, "no description")
|
||||||
|
|
||||||
def test_deduplication(self):
|
def test_deduplication(self):
|
||||||
c1 = Compound.create(
|
c1 = Compound.create(
|
||||||
self.package,
|
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",
|
||||||
name='Afoxolaner',
|
name="Afoxolaner",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
|
|
||||||
c2 = Compound.create(
|
c2 = Compound.create(
|
||||||
self.package,
|
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",
|
||||||
name='Afoxolaner',
|
name="Afoxolaner",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if create detects that this Compound already exist
|
# Check if create detects that this Compound already exist
|
||||||
@@ -109,36 +98,36 @@ class CompoundTest(TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_ = Compound.create(
|
_ = Compound.create(
|
||||||
self.package,
|
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',
|
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',
|
name="Afoxolaner",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_create_with_standardized_smiles(self):
|
def test_create_with_standardized_smiles(self):
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||||
name='Standardized SMILES',
|
name="Standardized SMILES",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
self.assertEqual(len(c.structures.all()), 1)
|
self.assertEqual(len(c.structures.all()), 1)
|
||||||
|
|
||||||
cs = c.structures.all()[0]
|
cs = c.structures.all()[0]
|
||||||
self.assertEqual(cs.normalized_structure, True)
|
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):
|
def test_create_with_non_standardized_smiles(self):
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='[O-][N+](=O)c1ccc(C(=O)[O-])cc1',
|
smiles="[O-][N+](=O)c1ccc(C(=O)[O-])cc1",
|
||||||
name='Non Standardized SMILES',
|
name="Non Standardized SMILES",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(len(c.structures.all()), 2)
|
self.assertEqual(len(c.structures.all()), 2)
|
||||||
for cs in c.structures.all():
|
for cs in c.structures.all():
|
||||||
if cs.normalized_structure:
|
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
|
break
|
||||||
else:
|
else:
|
||||||
# Loop finished without break, lets fail...
|
# Loop finished without break, lets fail...
|
||||||
@@ -147,51 +136,54 @@ class CompoundTest(TestCase):
|
|||||||
def test_add_structure_smoke(self):
|
def test_add_structure_smoke(self):
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||||
name='Standardized SMILES',
|
name="Standardized SMILES",
|
||||||
description='No Desc'
|
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)
|
self.assertEqual(len(c.structures.all()), 2)
|
||||||
|
|
||||||
def test_add_structure_with_different_normalized_smiles(self):
|
def test_add_structure_with_different_normalized_smiles(self):
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||||
name='Standardized SMILES',
|
name="Standardized SMILES",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
c.add_structure(
|
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',
|
"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')
|
"Different Standardized SMILES",
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
c = Compound.create(
|
c = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||||
name='Standardization Test',
|
name="Standardization Test",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
|
|
||||||
c.delete()
|
c.delete()
|
||||||
|
|
||||||
self.assertEqual(Compound.objects.filter(package=self.package).count(), 0)
|
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):
|
def test_set_as_default_structure(self):
|
||||||
c1 = Compound.create(
|
c1 = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||||
name='Standardized SMILES',
|
name="Standardized SMILES",
|
||||||
description='No Desc'
|
description="No Desc",
|
||||||
)
|
)
|
||||||
default_structure = c1.default_structure
|
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)
|
c1.set_default_structure(c2)
|
||||||
self.assertNotEqual(default_structure, c2)
|
self.assertNotEqual(default_structure, c2)
|
||||||
|
|||||||
+36
-34
@@ -1,6 +1,5 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from epdb.logic import PackageManager
|
from epdb.logic import PackageManager
|
||||||
from epdb.models import Compound, User, Reaction
|
from epdb.models import Compound, User, Reaction
|
||||||
@@ -12,50 +11,47 @@ class CopyTest(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(CopyTest, cls).setUpClass()
|
super(CopyTest, cls).setUpClass()
|
||||||
cls.user = User.objects.get(username='anonymous')
|
cls.user = User.objects.get(username="anonymous")
|
||||||
cls.package = PackageManager.create_package(cls.user, 'Source Package', 'No Desc')
|
cls.package = PackageManager.create_package(cls.user, "Source Package", "No Desc")
|
||||||
cls.AFOXOLANER = Compound.create(
|
cls.AFOXOLANER = Compound.create(
|
||||||
cls.package,
|
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',
|
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',
|
name="Afoxolaner",
|
||||||
description='Test compound for copying'
|
description="Test compound for copying",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.FOUR_NITROBENZOIC_ACID = Compound.create(
|
cls.FOUR_NITROBENZOIC_ACID = Compound.create(
|
||||||
cls.package,
|
cls.package,
|
||||||
smiles='[O-][N+](=O)c1ccc(C(=O)[O-])cc1', # Normalized: O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
smiles="[O-][N+](=O)c1ccc(C(=O)[O-])cc1", # Normalized: O=C(O)C1=CC=C([N+](=O)[O-])C=C1',
|
||||||
name='Test Compound',
|
name="Test Compound",
|
||||||
description='Compound with multiple structures'
|
description="Compound with multiple structures",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.ETHANOL = Compound.create(
|
cls.ETHANOL = Compound.create(
|
||||||
cls.package,
|
cls.package, smiles="CCO", name="Ethanol", description="Simple alcohol"
|
||||||
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.reaction_educt = Compound.create(
|
||||||
cls.package,
|
cls.package,
|
||||||
smiles='C(CCl)Cl',
|
smiles="C(CCl)Cl",
|
||||||
name='1,2-Dichloroethane',
|
name="1,2-Dichloroethane",
|
||||||
description='Eawag BBD compound c0001'
|
description="Eawag BBD compound c0001",
|
||||||
).default_structure
|
).default_structure
|
||||||
|
|
||||||
cls.reaction_product = Compound.create(
|
cls.reaction_product = Compound.create(
|
||||||
cls.package,
|
cls.package,
|
||||||
smiles='C(CO)Cl',
|
smiles="C(CO)Cl",
|
||||||
name='2-Chloroethanol',
|
name="2-Chloroethanol",
|
||||||
description='Eawag BBD compound c0005'
|
description="Eawag BBD compound c0005",
|
||||||
).default_structure
|
).default_structure
|
||||||
|
|
||||||
cls.REACTION = Reaction.create(
|
cls.REACTION = Reaction.create(
|
||||||
package=cls.package,
|
package=cls.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=[cls.reaction_educt],
|
educts=[cls.reaction_educt],
|
||||||
products=[cls.reaction_product],
|
products=[cls.reaction_product],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_compound_copy_basic(self):
|
def test_compound_copy_basic(self):
|
||||||
@@ -68,7 +64,9 @@ class CopyTest(TestCase):
|
|||||||
self.assertEqual(self.AFOXOLANER.description, copied_compound.description)
|
self.assertEqual(self.AFOXOLANER.description, copied_compound.description)
|
||||||
self.assertEqual(copied_compound.package, self.target_package)
|
self.assertEqual(copied_compound.package, self.target_package)
|
||||||
self.assertEqual(self.AFOXOLANER.package, self.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):
|
def test_compound_copy_with_multiple_structures(self):
|
||||||
"""Test copying a compound with multiple structures"""
|
"""Test copying a compound with multiple structures"""
|
||||||
@@ -86,7 +84,7 @@ class CopyTest(TestCase):
|
|||||||
self.assertIsNotNone(copied_compound.default_structure)
|
self.assertIsNotNone(copied_compound.default_structure)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
copied_compound.default_structure.smiles,
|
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):
|
def test_compound_copy_preserves_aliases(self):
|
||||||
@@ -95,15 +93,15 @@ class CopyTest(TestCase):
|
|||||||
original_compound = self.ETHANOL
|
original_compound = self.ETHANOL
|
||||||
|
|
||||||
# Add aliases if the method exists
|
# Add aliases if the method exists
|
||||||
if hasattr(original_compound, 'add_alias'):
|
if hasattr(original_compound, "add_alias"):
|
||||||
original_compound.add_alias('Ethyl alcohol')
|
original_compound.add_alias("Ethyl alcohol")
|
||||||
original_compound.add_alias('Grain alcohol')
|
original_compound.add_alias("Grain alcohol")
|
||||||
|
|
||||||
mapping = dict()
|
mapping = dict()
|
||||||
copied_compound = original_compound.copy(self.target_package, mapping)
|
copied_compound = original_compound.copy(self.target_package, mapping)
|
||||||
|
|
||||||
# Verify aliases were copied if they exist
|
# 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
|
original_aliases = original_compound.aliases
|
||||||
copied_aliases = copied_compound.aliases
|
copied_aliases = copied_compound.aliases
|
||||||
self.assertEqual(original_aliases, copied_aliases)
|
self.assertEqual(original_aliases, copied_aliases)
|
||||||
@@ -113,10 +111,10 @@ class CopyTest(TestCase):
|
|||||||
original_compound = self.ETHANOL
|
original_compound = self.ETHANOL
|
||||||
|
|
||||||
# Add external identifiers if the methods exist
|
# Add external identifiers if the methods exist
|
||||||
if hasattr(original_compound, 'add_cas_number'):
|
if hasattr(original_compound, "add_cas_number"):
|
||||||
original_compound.add_cas_number('64-17-5')
|
original_compound.add_cas_number("64-17-5")
|
||||||
if hasattr(original_compound, 'add_pubchem_compound_id'):
|
if hasattr(original_compound, "add_pubchem_compound_id"):
|
||||||
original_compound.add_pubchem_compound_id('702')
|
original_compound.add_pubchem_compound_id("702")
|
||||||
|
|
||||||
mapping = dict()
|
mapping = dict()
|
||||||
copied_compound = original_compound.copy(self.target_package, mapping)
|
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.smiles, copied_structure.smiles)
|
||||||
self.assertEqual(original_structure.canonical_smiles, copied_structure.canonical_smiles)
|
self.assertEqual(original_structure.canonical_smiles, copied_structure.canonical_smiles)
|
||||||
self.assertEqual(original_structure.inchikey, copied_structure.inchikey)
|
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
|
# Verify they are different objects
|
||||||
self.assertNotEqual(original_structure.uuid, copied_structure.uuid)
|
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.compound.package, self.package)
|
||||||
self.assertEqual(orig_educt.smiles, copy_educt.smiles)
|
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.assertNotEqual(orig_product.uuid, copy_product.uuid)
|
||||||
self.assertEqual(orig_product.name, copy_product.name)
|
self.assertEqual(orig_product.name, copy_product.name)
|
||||||
self.assertEqual(orig_product.description, copy_product.description)
|
self.assertEqual(orig_product.description, copy_product.description)
|
||||||
|
|||||||
+10
-10
@@ -11,21 +11,21 @@ class DatasetTest(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.cs1 = Compound.create(
|
self.cs1 = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
name='2,6-Dibromohydroquinone',
|
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',
|
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',
|
smiles="C1=C(C(=C(C=C1O)Br)O)Br",
|
||||||
).default_structure
|
).default_structure
|
||||||
|
|
||||||
self.cs2 = Compound.create(
|
self.cs2 = Compound.create(
|
||||||
self.package,
|
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
|
).default_structure
|
||||||
|
|
||||||
self.rule1 = Rule.create(
|
self.rule1 = Rule.create(
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
package=self.package,
|
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]',
|
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'
|
description="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1/simple-ambit-rule/f6a56c0f-a4a0-4ee3-b006-d765b4767cf6",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.reaction1 = Reaction.create(
|
self.reaction1 = Reaction.create(
|
||||||
@@ -33,14 +33,14 @@ class DatasetTest(TestCase):
|
|||||||
educts=[self.cs1],
|
educts=[self.cs1],
|
||||||
products=[self.cs2],
|
products=[self.cs2],
|
||||||
rules=[self.rule1],
|
rules=[self.rule1],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(DatasetTest, cls).setUpClass()
|
super(DatasetTest, cls).setUpClass()
|
||||||
cls.user = User.objects.get(username='anonymous')
|
cls.user = User.objects.get(username="anonymous")
|
||||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||||
|
|
||||||
def test_smoke(self):
|
def test_smoke(self):
|
||||||
reactions = [r for r in Reaction.objects.filter(package=self.package)]
|
reactions = [r for r in Reaction.objects.filter(package=self.package)]
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
from django.test import TestCase, tag
|
||||||
|
from epdb.logic import PackageManager
|
||||||
|
from epdb.models import User, EnviFormer, Package
|
||||||
|
|
||||||
|
|
||||||
|
@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.multigen_eval = True
|
||||||
|
mod.save()
|
||||||
|
mod.evaluate_model()
|
||||||
|
|
||||||
|
mod.predict("CCN(CC)C(=O)C1=CC(=CC=C1)C")
|
||||||
@@ -4,8 +4,7 @@ from utilities.chem import FormatConverter
|
|||||||
|
|
||||||
|
|
||||||
class FormatConverterTestCase(TestCase):
|
class FormatConverterTestCase(TestCase):
|
||||||
|
|
||||||
def test_standardization(self):
|
def test_standardization(self):
|
||||||
smiles = 'C[n+]1c([N-](C))cccc1'
|
smiles = "C[n+]1c([N-](C))cccc1"
|
||||||
standardized_smiles = FormatConverter.standardize(smiles)
|
standardized_smiles = FormatConverter.standardize(smiles)
|
||||||
self.assertEqual(standardized_smiles, 'CN=C1C=CC=CN1C')
|
self.assertEqual(standardized_smiles, "CN=C1C=CC=CN1C")
|
||||||
|
|||||||
+14
-11
@@ -4,7 +4,7 @@ import numpy as np
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from epdb.logic import PackageManager
|
from epdb.logic import PackageManager
|
||||||
from epdb.models import User, MLRelativeReasoning, RuleBasedRelativeReasoning, Package
|
from epdb.models import User, MLRelativeReasoning, Package
|
||||||
|
|
||||||
|
|
||||||
class ModelTest(TestCase):
|
class ModelTest(TestCase):
|
||||||
@@ -13,9 +13,9 @@ class ModelTest(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(ModelTest, cls).setUpClass()
|
super(ModelTest, cls).setUpClass()
|
||||||
cls.user = User.objects.get(username='anonymous')
|
cls.user = User.objects.get(username="anonymous")
|
||||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||||
cls.BBD_SUBSET = Package.objects.get(name='Fixtures')
|
cls.BBD_SUBSET = Package.objects.get(name="Fixtures")
|
||||||
|
|
||||||
def test_smoke(self):
|
def test_smoke(self):
|
||||||
with TemporaryDirectory() as tmpdir:
|
with TemporaryDirectory() as tmpdir:
|
||||||
@@ -24,7 +24,7 @@ class ModelTest(TestCase):
|
|||||||
|
|
||||||
rule_package_objs = [self.BBD_SUBSET]
|
rule_package_objs = [self.BBD_SUBSET]
|
||||||
data_package_objs = [self.BBD_SUBSET]
|
data_package_objs = [self.BBD_SUBSET]
|
||||||
eval_packages_objs = []
|
eval_packages_objs = [self.BBD_SUBSET]
|
||||||
|
|
||||||
mod = MLRelativeReasoning.create(
|
mod = MLRelativeReasoning.create(
|
||||||
self.package,
|
self.package,
|
||||||
@@ -32,8 +32,8 @@ class ModelTest(TestCase):
|
|||||||
data_package_objs,
|
data_package_objs,
|
||||||
eval_packages_objs,
|
eval_packages_objs,
|
||||||
threshold=threshold,
|
threshold=threshold,
|
||||||
name='ECC - BBD - 0.5',
|
name="ECC - BBD - 0.5",
|
||||||
description='Created MLRelativeReasoning in Testcase',
|
description="Created MLRelativeReasoning in Testcase",
|
||||||
)
|
)
|
||||||
|
|
||||||
# mod = RuleBasedRelativeReasoning.create(
|
# mod = RuleBasedRelativeReasoning.create(
|
||||||
@@ -52,9 +52,9 @@ class ModelTest(TestCase):
|
|||||||
mod.build_model()
|
mod.build_model()
|
||||||
mod.multigen_eval = True
|
mod.multigen_eval = True
|
||||||
mod.save()
|
mod.save()
|
||||||
# mod.evaluate_model()
|
mod.evaluate_model()
|
||||||
|
|
||||||
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()
|
products = dict()
|
||||||
for r in results:
|
for r in results:
|
||||||
@@ -62,8 +62,11 @@ class ModelTest(TestCase):
|
|||||||
products[tuple(sorted(ps.product_set))] = (r.rule.name, r.probability)
|
products[tuple(sorted(ps.product_set))] = (r.rule.name, r.probability)
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
('CC=O', 'CCNC(=O)C1=CC(C)=CC=C1'): ('bt0243-4301', np.float64(0.33333333333333337)),
|
("CC=O", "CCNC(=O)C1=CC(C)=CC=C1"): (
|
||||||
('CC1=CC=CC(C(=O)O)=C1', 'CCNCC'): ('bt0430-4011', np.float64(0.25)),
|
"bt0243-4301",
|
||||||
|
np.float64(0.33333333333333337),
|
||||||
|
),
|
||||||
|
("CC1=CC=CC(C(=O)O)=C1", "CCNCC"): ("bt0430-4011", np.float64(0.25)),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assertEqual(products, expected)
|
self.assertEqual(products, expected)
|
||||||
|
|||||||
+69
-31
@@ -1,4 +1,3 @@
|
|||||||
import json
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from networkx.utils.misc import graphs_equal
|
from networkx.utils.misc import graphs_equal
|
||||||
from epdb.logic import PackageManager, SPathway
|
from epdb.logic import PackageManager, SPathway
|
||||||
@@ -12,9 +11,11 @@ class MultiGenTest(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(MultiGenTest, cls).setUpClass()
|
super(MultiGenTest, cls).setUpClass()
|
||||||
cls.user: 'User' = User.objects.get(username='anonymous')
|
cls.user: "User" = User.objects.get(username="anonymous")
|
||||||
cls.package: 'Package' = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
cls.package: "Package" = PackageManager.create_package(
|
||||||
cls.BBD_SUBSET: 'Package' = Package.objects.get(name='Fixtures')
|
cls.user, "Anon Test Package", "No Desc"
|
||||||
|
)
|
||||||
|
cls.BBD_SUBSET: "Package" = Package.objects.get(name="Fixtures")
|
||||||
|
|
||||||
def test_equal_pathways(self):
|
def test_equal_pathways(self):
|
||||||
"""Test that two identical pathways return a precision and recall of 1.0"""
|
"""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
|
if len(pathway.edge_set.all()) == 0: # Do not test pathways with no edges
|
||||||
continue
|
continue
|
||||||
score, precision, recall = multigen_eval(pathway, pathway)
|
score, precision, recall = multigen_eval(pathway, pathway)
|
||||||
self.assertEqual(precision, 1.0, f"Precision should be one for identical pathways. "
|
self.assertEqual(
|
||||||
f"Failed on pathway: {pathway.name}")
|
precision,
|
||||||
self.assertEqual(recall, 1.0, f"Recall should be one for identical pathways. "
|
1.0,
|
||||||
f"Failed on pathway: {pathway.name}")
|
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):
|
def test_intermediates(self):
|
||||||
"""Test that an intermediate can be correctly identified and the metrics are correctly adjusted"""
|
"""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(len(intermediates), 1, "There should be 1 found intermediate")
|
||||||
self.assertEqual(precision, 1, "Precision should be 1")
|
self.assertEqual(precision, 1, "Precision should be 1")
|
||||||
self.assertEqual(recall, 1, "Recall should be 1")
|
self.assertEqual(recall, 1, "Recall should be 1")
|
||||||
@@ -49,7 +59,9 @@ class MultiGenTest(TestCase):
|
|||||||
|
|
||||||
def test_all(self):
|
def test_all(self):
|
||||||
"""Test an intermediate, false-positive and false-negative together"""
|
"""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.assertEqual(len(intermediates), 1, "There should be 1 found intermediate")
|
||||||
self.assertAlmostEqual(precision, 0.6, 3, "Precision should be 0.6")
|
self.assertAlmostEqual(precision, 0.6, 3, "Precision should be 0.6")
|
||||||
self.assertAlmostEqual(recall, 0.75, 3, "Recall should be 0.75")
|
self.assertAlmostEqual(recall, 0.75, 3, "Recall should be 0.75")
|
||||||
@@ -57,19 +69,23 @@ class MultiGenTest(TestCase):
|
|||||||
def test_shallow_pathway(self):
|
def test_shallow_pathway(self):
|
||||||
pathways = self.BBD_SUBSET.pathways.all()
|
pathways = self.BBD_SUBSET.pathways.all()
|
||||||
for pathway in pathways:
|
for pathway in pathways:
|
||||||
pathway_name = pathway.name
|
|
||||||
if len(pathway.edge_set.all()) == 0: # Do not test pathways with no edges
|
if len(pathway.edge_set.all()) == 0: # Do not test pathways with no edges
|
||||||
continue
|
continue
|
||||||
|
|
||||||
shallow_pathway = graph_from_pathway(SPathway.from_pathway(pathway))
|
shallow_pathway = graph_from_pathway(SPathway.from_pathway(pathway))
|
||||||
pathway = graph_from_pathway(pathway)
|
pathway = graph_from_pathway(pathway)
|
||||||
|
|
||||||
if not graphs_equal(shallow_pathway, pathway):
|
if not graphs_equal(shallow_pathway, pathway):
|
||||||
print('\n\nS', shallow_pathway.adj)
|
print("\n\nS", shallow_pathway.adj)
|
||||||
print('\n\nPW', pathway.adj)
|
print("\n\nPW", pathway.adj)
|
||||||
# print(shallow_pathway.nodes, pathway.nodes)
|
# print(shallow_pathway.nodes, pathway.nodes)
|
||||||
# print(shallow_pathway.graph, pathway.graph)
|
# print(shallow_pathway.graph, pathway.graph)
|
||||||
|
|
||||||
self.assertTrue(graphs_equal(shallow_pathway, pathway), f"Networkx graph from shallow pathway not "
|
self.assertTrue(
|
||||||
f"equal to pathway for pathway {pathway.name}")
|
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):
|
def test_graph_edit_eval(self):
|
||||||
"""Performs all the previous tests but with graph_edit_eval
|
"""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
|
if len(pathway.edge_set.all()) == 0: # Do not test pathways with no edges
|
||||||
continue
|
continue
|
||||||
score = pathway_edit_eval(pathway, pathway)
|
score = pathway_edit_eval(pathway, pathway)
|
||||||
self.assertEqual(score, 0.0, "Pathway edit distance should be zero for identical pathways. "
|
self.assertEqual(
|
||||||
f"Failed on pathway: {pathway.name}")
|
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())
|
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())
|
fp_score = pathway_edit_eval(*self.fp_case())
|
||||||
self.assertAlmostEqual(fp_score, 1.25, 3, "Pathway edit distance failed on fp case")
|
self.assertAlmostEqual(fp_score, 1.25, 3, "Pathway edit distance failed on fp case")
|
||||||
fn_score = pathway_edit_eval(*self.fn_case())
|
fn_score = pathway_edit_eval(*self.fn_case())
|
||||||
@@ -93,22 +115,30 @@ class MultiGenTest(TestCase):
|
|||||||
def intermediate_case(self):
|
def intermediate_case(self):
|
||||||
"""Create an example with an intermediate in the predicted pathway"""
|
"""Create an example with an intermediate in the predicted pathway"""
|
||||||
true_pathway = Pathway.create(self.package, "CCO")
|
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 = Pathway.create(self.package, "CCO")
|
||||||
pred_pathway.add_edge([pred_pathway.root_nodes.all()[0]],
|
pred_pathway.add_edge(
|
||||||
[acetaldehyde := pred_pathway.add_node("CC=O", depth=1)])
|
[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("CC(=O)O", depth=2)])
|
||||||
return true_pathway, pred_pathway
|
return true_pathway, pred_pathway
|
||||||
|
|
||||||
def fp_case(self):
|
def fp_case(self):
|
||||||
"""Create an example with an extra compound in the predicted pathway"""
|
"""Create an example with an extra compound in the predicted pathway"""
|
||||||
true_pathway = Pathway.create(self.package, "CCO")
|
true_pathway = Pathway.create(self.package, "CCO")
|
||||||
true_pathway.add_edge([true_pathway.root_nodes.all()[0]],
|
true_pathway.add_edge(
|
||||||
[acetaldehyde := true_pathway.add_node("CC=O", depth=1)])
|
[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)])
|
true_pathway.add_edge([acetaldehyde], [true_pathway.add_node("CC(=O)O", depth=2)])
|
||||||
pred_pathway = Pathway.create(self.package, "CCO")
|
pred_pathway = Pathway.create(self.package, "CCO")
|
||||||
pred_pathway.add_edge([pred_pathway.root_nodes.all()[0]],
|
pred_pathway.add_edge(
|
||||||
[acetaldehyde := pred_pathway.add_node("CC=O", depth=1)])
|
[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("CC(=O)O", depth=2)])
|
||||||
pred_pathway.add_edge([acetaldehyde], [pred_pathway.add_node("C", depth=2)])
|
pred_pathway.add_edge([acetaldehyde], [pred_pathway.add_node("C", depth=2)])
|
||||||
return true_pathway, pred_pathway
|
return true_pathway, pred_pathway
|
||||||
@@ -116,22 +146,30 @@ class MultiGenTest(TestCase):
|
|||||||
def fn_case(self):
|
def fn_case(self):
|
||||||
"""Create an example with a missing compound in the predicted pathway"""
|
"""Create an example with a missing compound in the predicted pathway"""
|
||||||
true_pathway = Pathway.create(self.package, "CCO")
|
true_pathway = Pathway.create(self.package, "CCO")
|
||||||
true_pathway.add_edge([true_pathway.root_nodes.all()[0]],
|
true_pathway.add_edge(
|
||||||
[acetaldehyde := true_pathway.add_node("CC=O", depth=1)])
|
[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)])
|
true_pathway.add_edge([acetaldehyde], [true_pathway.add_node("CC(=O)O", depth=2)])
|
||||||
pred_pathway = Pathway.create(self.package, "CCO")
|
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
|
return true_pathway, pred_pathway
|
||||||
|
|
||||||
def all_case(self):
|
def all_case(self):
|
||||||
"""Create an example with an intermediate, extra compound and missing compound"""
|
"""Create an example with an intermediate, extra compound and missing compound"""
|
||||||
true_pathway = Pathway.create(self.package, "CCO")
|
true_pathway = Pathway.create(self.package, "CCO")
|
||||||
true_pathway.add_edge([true_pathway.root_nodes.all()[0]],
|
true_pathway.add_edge(
|
||||||
[acetaldehyde := true_pathway.add_node("CC=O", depth=1)])
|
[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("C", depth=2)])
|
||||||
true_pathway.add_edge([acetaldehyde], [true_pathway.add_node("CC(=O)O", 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 = 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("CC=O", depth=2)])
|
||||||
pred_pathway.add_edge([methane], [true_pathway.add_node("c1ccccc1", depth=2)])
|
pred_pathway.add_edge([methane], [true_pathway.add_node("c1ccccc1", depth=2)])
|
||||||
return true_pathway, pred_pathway
|
return true_pathway, pred_pathway
|
||||||
|
|||||||
@@ -10,127 +10,127 @@ class ReactionTest(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(ReactionTest, cls).setUpClass()
|
super(ReactionTest, cls).setUpClass()
|
||||||
cls.user = User.objects.get(username='anonymous')
|
cls.user = User.objects.get(username="anonymous")
|
||||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||||
|
|
||||||
def test_smoke(self):
|
def test_smoke(self):
|
||||||
educt = Compound.create(
|
educt = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='C(CCl)Cl',
|
smiles="C(CCl)Cl",
|
||||||
name='1,2-Dichloroethane',
|
name="1,2-Dichloroethane",
|
||||||
description='Eawag BBD compound c0001'
|
description="Eawag BBD compound c0001",
|
||||||
).default_structure
|
).default_structure
|
||||||
|
|
||||||
product = Compound.create(
|
product = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='C(CO)Cl',
|
smiles="C(CO)Cl",
|
||||||
name='2-Chloroethanol',
|
name="2-Chloroethanol",
|
||||||
description='Eawag BBD compound c0005'
|
description="Eawag BBD compound c0005",
|
||||||
).default_structure
|
).default_structure
|
||||||
|
|
||||||
r = Reaction.create(
|
r = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=[educt],
|
educts=[educt],
|
||||||
products=[product],
|
products=[product],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(r.smirks(), 'C(CCl)Cl>>C(CO)Cl')
|
self.assertEqual(r.smirks(), "C(CCl)Cl>>C(CO)Cl")
|
||||||
self.assertEqual(r.name, 'Eawag BBD reaction r0001')
|
self.assertEqual(r.name, "Eawag BBD reaction r0001")
|
||||||
self.assertEqual(r.description, 'no description')
|
self.assertEqual(r.description, "no description")
|
||||||
|
|
||||||
def test_string_educts_and_products(self):
|
def test_string_educts_and_products(self):
|
||||||
r = Reaction.create(
|
r = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=['C(CCl)Cl'],
|
educts=["C(CCl)Cl"],
|
||||||
products=['C(CO)Cl'],
|
products=["C(CO)Cl"],
|
||||||
multi_step=False
|
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):
|
def test_missing_smiles(self):
|
||||||
educt = Compound.create(
|
educt = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='C(CCl)Cl',
|
smiles="C(CCl)Cl",
|
||||||
name='1,2-Dichloroethane',
|
name="1,2-Dichloroethane",
|
||||||
description='Eawag BBD compound c0001'
|
description="Eawag BBD compound c0001",
|
||||||
).default_structure
|
).default_structure
|
||||||
|
|
||||||
product = Compound.create(
|
product = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles='C(CO)Cl',
|
smiles="C(CO)Cl",
|
||||||
name='2-Chloroethanol',
|
name="2-Chloroethanol",
|
||||||
description='Eawag BBD compound c0005'
|
description="Eawag BBD compound c0005",
|
||||||
).default_structure
|
).default_structure
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_ = Reaction.create(
|
_ = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=[educt],
|
educts=[educt],
|
||||||
products=[],
|
products=[],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_ = Reaction.create(
|
_ = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=[],
|
educts=[],
|
||||||
products=[product],
|
products=[product],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_ = Reaction.create(
|
_ = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=[],
|
educts=[],
|
||||||
products=[],
|
products=[],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_empty_name_and_description_are_ignored(self):
|
def test_empty_name_and_description_are_ignored(self):
|
||||||
r = Reaction.create(
|
r = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='',
|
name="",
|
||||||
description='',
|
description="",
|
||||||
educts=['C(CCl)Cl'],
|
educts=["C(CCl)Cl"],
|
||||||
products=['C(CO)Cl'],
|
products=["C(CO)Cl"],
|
||||||
multi_step=False,
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(r.name, 'no name')
|
self.assertEqual(r.name, "no name")
|
||||||
self.assertEqual(r.description, 'no description')
|
self.assertEqual(r.description, "no description")
|
||||||
|
|
||||||
def test_deduplication(self):
|
def test_deduplication(self):
|
||||||
rule = Rule.create(
|
rule = Rule.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
name='bt0022-2833',
|
name="bt0022-2833",
|
||||||
description='Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative',
|
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]',
|
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(
|
r1 = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=['C(CCl)Cl'],
|
educts=["C(CCl)Cl"],
|
||||||
products=['C(CO)Cl'],
|
products=["C(CO)Cl"],
|
||||||
rules=[rule],
|
rules=[rule],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
r2 = Reaction.create(
|
r2 = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=['C(CCl)Cl'],
|
educts=["C(CCl)Cl"],
|
||||||
products=['C(CO)Cl'],
|
products=["C(CO)Cl"],
|
||||||
rules=[rule],
|
rules=[rule],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if create detects that this Compound already exist
|
# Check if create detects that this Compound already exist
|
||||||
@@ -141,18 +141,18 @@ class ReactionTest(TestCase):
|
|||||||
def test_deduplication_without_rules(self):
|
def test_deduplication_without_rules(self):
|
||||||
r1 = Reaction.create(
|
r1 = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=['C(CCl)Cl'],
|
educts=["C(CCl)Cl"],
|
||||||
products=['C(CO)Cl'],
|
products=["C(CO)Cl"],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
r2 = Reaction.create(
|
r2 = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=['C(CCl)Cl'],
|
educts=["C(CCl)Cl"],
|
||||||
products=['C(CO)Cl'],
|
products=["C(CO)Cl"],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if create detects that this Compound already exist
|
# Check if create detects that this Compound already exist
|
||||||
@@ -164,19 +164,19 @@ class ReactionTest(TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_ = Reaction.create(
|
_ = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=['ASDF'],
|
educts=["ASDF"],
|
||||||
products=['C(CO)Cl'],
|
products=["C(CO)Cl"],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
r = Reaction.create(
|
r = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='Eawag BBD reaction r0001',
|
name="Eawag BBD reaction r0001",
|
||||||
educts=['C(CCl)Cl'],
|
educts=["C(CCl)Cl"],
|
||||||
products=['C(CO)Cl'],
|
products=["C(CO)Cl"],
|
||||||
multi_step=False
|
multi_step=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
r.delete()
|
r.delete()
|
||||||
|
|||||||
+50
-44
@@ -10,73 +10,79 @@ class RuleTest(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(RuleTest, cls).setUpClass()
|
super(RuleTest, cls).setUpClass()
|
||||||
cls.user = User.objects.get(username='anonymous')
|
cls.user = User.objects.get(username="anonymous")
|
||||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||||
|
|
||||||
def test_smoke(self):
|
def test_smoke(self):
|
||||||
r = Rule.create(
|
r = Rule.create(
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='bt0022-2833',
|
name="bt0022-2833",
|
||||||
description='Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative',
|
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]',
|
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,
|
self.assertEqual(
|
||||||
'[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]')
|
r.smirks,
|
||||||
self.assertEqual(r.name, 'bt0022-2833')
|
"[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.description,
|
)
|
||||||
'Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative')
|
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):
|
def test_smirks_are_trimmed(self):
|
||||||
r = Rule.create(
|
r = Rule.create(
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name='bt0022-2833',
|
name="bt0022-2833",
|
||||||
description='Dihalomethyl derivative + Halomethyl derivative > 1-Halo-1-methylalcohol derivative + 1-Methylalcohol derivative',
|
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] ',
|
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,
|
self.assertEqual(
|
||||||
'[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]')
|
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):
|
def test_name_and_description_optional(self):
|
||||||
r = Rule.create(
|
r = Rule.create(
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
package=self.package,
|
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.assertRegex(r.name, "Rule \\d+")
|
||||||
self.assertEqual(r.description, 'no description')
|
self.assertEqual(r.description, "no description")
|
||||||
|
|
||||||
def test_empty_name_and_description_are_ignored(self):
|
def test_empty_name_and_description_are_ignored(self):
|
||||||
r = Rule.create(
|
r = Rule.create(
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
package=self.package,
|
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]",
|
||||||
name='',
|
name="",
|
||||||
description='',
|
description="",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertRegex(r.name, 'Rule \\d+')
|
self.assertRegex(r.name, "Rule \\d+")
|
||||||
self.assertEqual(r.description, 'no description')
|
self.assertEqual(r.description, "no description")
|
||||||
|
|
||||||
def test_deduplication(self):
|
def test_deduplication(self):
|
||||||
r1 = Rule.create(
|
r1 = Rule.create(
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
package=self.package,
|
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]",
|
||||||
name='',
|
name="",
|
||||||
description='',
|
description="",
|
||||||
)
|
)
|
||||||
|
|
||||||
r2 = Rule.create(
|
r2 = Rule.create(
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
package=self.package,
|
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]",
|
||||||
name='',
|
name="",
|
||||||
description='',
|
description="",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(r1.pk, r2.pk)
|
self.assertEqual(r1.pk, r2.pk)
|
||||||
@@ -84,21 +90,21 @@ class RuleTest(TestCase):
|
|||||||
|
|
||||||
def test_valid_smirks(self):
|
def test_valid_smirks(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
r = Rule.create(
|
Rule.create(
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
package=self.package,
|
package=self.package,
|
||||||
smirks='This is not a valid SMIRKS',
|
smirks="This is not a valid SMIRKS",
|
||||||
name='',
|
name="",
|
||||||
description='',
|
description="",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
r = Rule.create(
|
r = Rule.create(
|
||||||
rule_type='SimpleAmbitRule',
|
rule_type="SimpleAmbitRule",
|
||||||
package=self.package,
|
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]",
|
||||||
name='',
|
name="",
|
||||||
description='',
|
description="",
|
||||||
)
|
)
|
||||||
|
|
||||||
r.delete()
|
r.delete()
|
||||||
|
|||||||
+361
-313
File diff suppressed because it is too large
Load Diff
+109
-132
@@ -12,34 +12,32 @@ class SimpleAmbitRuleTest(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(SimpleAmbitRuleTest, cls).setUpClass()
|
super(SimpleAmbitRuleTest, cls).setUpClass()
|
||||||
cls.user = User.objects.get(username='anonymous')
|
cls.user = User.objects.get(username="anonymous")
|
||||||
cls.package = PackageManager.create_package(cls.user, 'Simple Ambit Rule Test Package',
|
cls.package = PackageManager.create_package(
|
||||||
'Test Package for SimpleAmbitRule')
|
cls.user, "Simple Ambit Rule Test Package", "Test Package for SimpleAmbitRule"
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_basic_rule(self):
|
def test_create_basic_rule(self):
|
||||||
"""Test creating a basic SimpleAmbitRule with minimal parameters."""
|
"""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(
|
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||||
package=self.package,
|
|
||||||
smirks=smirks
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertIsInstance(rule, SimpleAmbitRule)
|
self.assertIsInstance(rule, SimpleAmbitRule)
|
||||||
self.assertEqual(rule.smirks, smirks)
|
self.assertEqual(rule.smirks, smirks)
|
||||||
self.assertEqual(rule.package, self.package)
|
self.assertEqual(rule.package, self.package)
|
||||||
self.assertRegex(rule.name, r'Rule \d+')
|
self.assertRegex(rule.name, r"Rule \d+")
|
||||||
self.assertEqual(rule.description, 'no description')
|
self.assertEqual(rule.description, "no description")
|
||||||
self.assertIsNone(rule.reactant_filter_smarts)
|
self.assertIsNone(rule.reactant_filter_smarts)
|
||||||
self.assertIsNone(rule.product_filter_smarts)
|
self.assertIsNone(rule.product_filter_smarts)
|
||||||
|
|
||||||
def test_create_with_all_parameters(self):
|
def test_create_with_all_parameters(self):
|
||||||
"""Test creating SimpleAmbitRule with all parameters."""
|
"""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]'
|
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'
|
name = "Test Rule"
|
||||||
description = 'A test biotransformation rule'
|
description = "A test biotransformation rule"
|
||||||
reactant_filter = '[CH2X4]'
|
reactant_filter = "[CH2X4]"
|
||||||
product_filter = '[OH]'
|
product_filter = "[OH]"
|
||||||
|
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
@@ -47,7 +45,7 @@ class SimpleAmbitRuleTest(TestCase):
|
|||||||
description=description,
|
description=description,
|
||||||
smirks=smirks,
|
smirks=smirks,
|
||||||
reactant_filter_smarts=reactant_filter,
|
reactant_filter_smarts=reactant_filter,
|
||||||
product_filter_smarts=product_filter
|
product_filter_smarts=product_filter,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(rule.name, name)
|
self.assertEqual(rule.name, name)
|
||||||
@@ -60,127 +58,114 @@ class SimpleAmbitRuleTest(TestCase):
|
|||||||
"""Test that SMIRKS is required for rule creation."""
|
"""Test that SMIRKS is required for rule creation."""
|
||||||
with self.assertRaises(ValueError) as cm:
|
with self.assertRaises(ValueError) as cm:
|
||||||
SimpleAmbitRule.create(package=self.package, smirks=None)
|
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:
|
with self.assertRaises(ValueError) as cm:
|
||||||
SimpleAmbitRule.create(package=self.package, smirks='')
|
SimpleAmbitRule.create(package=self.package, smirks="")
|
||||||
self.assertIn('SMIRKS is required', str(cm.exception))
|
self.assertIn("SMIRKS is required", str(cm.exception))
|
||||||
|
|
||||||
with self.assertRaises(ValueError) as cm:
|
with self.assertRaises(ValueError) as cm:
|
||||||
SimpleAmbitRule.create(package=self.package, smirks=' ')
|
SimpleAmbitRule.create(package=self.package, smirks=" ")
|
||||||
self.assertIn('SMIRKS is required', str(cm.exception))
|
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):
|
def test_invalid_smirks_validation(self, mock_is_valid):
|
||||||
"""Test validation of SMIRKS format."""
|
"""Test validation of SMIRKS format."""
|
||||||
mock_is_valid.return_value = False
|
mock_is_valid.return_value = False
|
||||||
|
|
||||||
invalid_smirks = 'invalid_smirks_string'
|
invalid_smirks = "invalid_smirks_string"
|
||||||
with self.assertRaises(ValueError) as cm:
|
with self.assertRaises(ValueError) as cm:
|
||||||
SimpleAmbitRule.create(
|
SimpleAmbitRule.create(package=self.package, smirks=invalid_smirks)
|
||||||
package=self.package,
|
|
||||||
smirks=invalid_smirks
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertIn(f'SMIRKS "{invalid_smirks}" is invalid', str(cm.exception))
|
self.assertIn(f'SMIRKS "{invalid_smirks}" is invalid', str(cm.exception))
|
||||||
mock_is_valid.assert_called_once_with(invalid_smirks)
|
mock_is_valid.assert_called_once_with(invalid_smirks)
|
||||||
|
|
||||||
def test_smirks_trimming(self):
|
def test_smirks_trimming(self):
|
||||||
"""Test that SMIRKS strings are trimmed during creation."""
|
"""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 = "[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_with_whitespace = f" {smirks} "
|
||||||
|
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks_with_whitespace)
|
||||||
package=self.package,
|
|
||||||
smirks=smirks_with_whitespace
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(rule.smirks, smirks)
|
self.assertEqual(rule.smirks, smirks)
|
||||||
|
|
||||||
def test_empty_name_and_description_handling(self):
|
def test_empty_name_and_description_handling(self):
|
||||||
"""Test that empty name and description are handled appropriately."""
|
"""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(
|
rule = SimpleAmbitRule.create(
|
||||||
package=self.package,
|
package=self.package, smirks=smirks, name="", description=" "
|
||||||
smirks=smirks,
|
|
||||||
name='',
|
|
||||||
description=' '
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertRegex(rule.name, r'Rule \d+')
|
self.assertRegex(rule.name, r"Rule \d+")
|
||||||
self.assertEqual(rule.description, 'no description')
|
self.assertEqual(rule.description, "no description")
|
||||||
|
|
||||||
def test_deduplication_basic(self):
|
def test_deduplication_basic(self):
|
||||||
"""Test that identical rules are deduplicated."""
|
"""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(
|
rule1 = SimpleAmbitRule.create(package=self.package, smirks=smirks, name="Rule 1")
|
||||||
package=self.package,
|
|
||||||
smirks=smirks,
|
|
||||||
name='Rule 1'
|
|
||||||
)
|
|
||||||
|
|
||||||
rule2 = SimpleAmbitRule.create(
|
rule2 = SimpleAmbitRule.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
smirks=smirks,
|
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(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):
|
def test_deduplication_with_filters(self):
|
||||||
"""Test deduplication with filter SMARTS."""
|
"""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]'
|
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]'
|
reactant_filter = "[CH2X4]"
|
||||||
product_filter = '[OH]'
|
product_filter = "[OH]"
|
||||||
|
|
||||||
rule1 = SimpleAmbitRule.create(
|
rule1 = SimpleAmbitRule.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
smirks=smirks,
|
smirks=smirks,
|
||||||
reactant_filter_smarts=reactant_filter,
|
reactant_filter_smarts=reactant_filter,
|
||||||
product_filter_smarts=product_filter
|
product_filter_smarts=product_filter,
|
||||||
)
|
)
|
||||||
|
|
||||||
rule2 = SimpleAmbitRule.create(
|
rule2 = SimpleAmbitRule.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
smirks=smirks,
|
smirks=smirks,
|
||||||
reactant_filter_smarts=reactant_filter,
|
reactant_filter_smarts=reactant_filter,
|
||||||
product_filter_smarts=product_filter
|
product_filter_smarts=product_filter,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(rule1.pk, rule2.pk)
|
self.assertEqual(rule1.pk, rule2.pk)
|
||||||
|
|
||||||
def test_no_deduplication_different_filters(self):
|
def test_no_deduplication_different_filters(self):
|
||||||
"""Test that rules with different filters are not deduplicated."""
|
"""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(
|
rule1 = SimpleAmbitRule.create(
|
||||||
package=self.package,
|
package=self.package, smirks=smirks, reactant_filter_smarts="[CH2X4]"
|
||||||
smirks=smirks,
|
|
||||||
reactant_filter_smarts='[CH2X4]'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
rule2 = SimpleAmbitRule.create(
|
rule2 = SimpleAmbitRule.create(
|
||||||
package=self.package,
|
package=self.package, smirks=smirks, reactant_filter_smarts="[CH3X4]"
|
||||||
smirks=smirks,
|
|
||||||
reactant_filter_smarts='[CH3X4]'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertNotEqual(rule1.pk, rule2.pk)
|
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):
|
def test_filter_smarts_trimming(self):
|
||||||
"""Test that filter SMARTS are trimmed and handled correctly."""
|
"""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)
|
# Test with whitespace-only filters (should be treated as None)
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
smirks=smirks,
|
smirks=smirks,
|
||||||
reactant_filter_smarts=' ',
|
reactant_filter_smarts=" ",
|
||||||
product_filter_smarts=' '
|
product_filter_smarts=" ",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertIsNone(rule.reactant_filter_smarts)
|
self.assertIsNone(rule.reactant_filter_smarts)
|
||||||
@@ -188,94 +173,85 @@ class SimpleAmbitRuleTest(TestCase):
|
|||||||
|
|
||||||
def test_url_property(self):
|
def test_url_property(self):
|
||||||
"""Test the URL property generation."""
|
"""Test the URL property generation."""
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||||
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)
|
self.assertEqual(rule.url, expected_url)
|
||||||
|
|
||||||
@patch('epdb.models.FormatConverter.apply')
|
@patch("epdb.models.FormatConverter.apply")
|
||||||
def test_apply_method(self, mock_apply):
|
def test_apply_method(self, mock_apply):
|
||||||
"""Test the apply method delegates to FormatConverter."""
|
"""Test the apply method delegates to FormatConverter."""
|
||||||
mock_apply.return_value = ['product1', 'product2']
|
mock_apply.return_value = ["product1", "product2"]
|
||||||
|
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||||
package=self.package,
|
|
||||||
smirks='[H:1][C:2]>>[H:1][O:2]'
|
|
||||||
)
|
|
||||||
|
|
||||||
test_smiles = 'CCO'
|
test_smiles = "CCO"
|
||||||
result = rule.apply(test_smiles)
|
result = rule.apply(test_smiles)
|
||||||
|
|
||||||
mock_apply.assert_called_once_with(test_smiles, rule.smirks)
|
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):
|
def test_reactants_smarts_property(self):
|
||||||
"""Test reactants_smarts property extracts correct part of SMIRKS."""
|
"""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]'
|
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]'
|
expected_reactants = "[H:5][C:1]([#6:6])([#1,#9,#17,#35,#53:4])[#9,#17,#35,#53]"
|
||||||
|
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||||
package=self.package,
|
|
||||||
smirks=smirks
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(rule.reactants_smarts, expected_reactants)
|
self.assertEqual(rule.reactants_smarts, expected_reactants)
|
||||||
|
|
||||||
def test_products_smarts_property(self):
|
def test_products_smarts_property(self):
|
||||||
"""Test products_smarts property extracts correct part of SMIRKS."""
|
"""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]'
|
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]'
|
expected_products = "[H:5][C:1]([#6:6])([#8])[#1,#9,#17,#35,#53:4]"
|
||||||
|
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||||
package=self.package,
|
|
||||||
smirks=smirks
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(rule.products_smarts, expected_products)
|
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):
|
def test_related_reactions_property(self, mock_package_objects):
|
||||||
"""Test related_reactions property returns correct queryset."""
|
"""Test related_reactions property returns correct queryset."""
|
||||||
mock_qs = MagicMock()
|
mock_qs = MagicMock()
|
||||||
mock_package_objects.filter.return_value = mock_qs
|
mock_package_objects.filter.return_value = mock_qs
|
||||||
|
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||||
package=self.package,
|
|
||||||
smirks='[H:1][C:2]>>[H:1][O:2]'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Instead of directly assigning, patch the property or use with patch.object
|
# 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:
|
with patch.object(
|
||||||
mock_reaction_rule.return_value.filter.return_value.order_by.return_value = ['reaction1', 'reaction2']
|
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
|
result = rule.related_reactions
|
||||||
|
|
||||||
mock_package_objects.filter.assert_called_once_with(reviewed=True)
|
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.assert_called_once_with(package__in=mock_qs)
|
||||||
mock_reaction_rule.return_value.filter.return_value.order_by.assert_called_once_with('name')
|
mock_reaction_rule.return_value.filter.return_value.order_by.assert_called_once_with(
|
||||||
self.assertEqual(result, ['reaction1', 'reaction2'])
|
"name"
|
||||||
|
)
|
||||||
|
self.assertEqual(result, ["reaction1", "reaction2"])
|
||||||
|
|
||||||
@patch('epdb.models.Pathway.objects')
|
@patch("epdb.models.Pathway.objects")
|
||||||
@patch('epdb.models.Edge.objects')
|
@patch("epdb.models.Edge.objects")
|
||||||
def test_related_pathways_property(self, mock_edge_objects, mock_pathway_objects):
|
def test_related_pathways_property(self, mock_edge_objects, mock_pathway_objects):
|
||||||
"""Test related_pathways property returns correct queryset."""
|
"""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
|
mock_prop.return_value = mock_related_reactions
|
||||||
|
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||||
package=self.package,
|
|
||||||
smirks='[H:1][C:2]>>[H:1][O:2]'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mock Edge objects query
|
# Mock Edge objects query
|
||||||
mock_edge_values = MagicMock()
|
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_edge_objects.filter.return_value = mock_edge_values
|
||||||
|
|
||||||
# Mock Pathway objects query
|
# Mock Pathway objects query
|
||||||
@@ -285,52 +261,49 @@ class SimpleAmbitRuleTest(TestCase):
|
|||||||
result = rule.related_pathways
|
result = rule.related_pathways
|
||||||
|
|
||||||
mock_edge_objects.filter.assert_called_once_with(edge_label__in=mock_related_reactions)
|
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()
|
mock_pathway_objects.filter.assert_called_once()
|
||||||
self.assertEqual(result, mock_pathway_qs)
|
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):
|
def test_as_svg_property(self, mock_smirks_to_svg):
|
||||||
"""Test as_svg property calls IndigoUtils correctly."""
|
"""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(
|
rule = SimpleAmbitRule.create(package=self.package, smirks="[H:1][C:2]>>[H:1][O:2]")
|
||||||
package=self.package,
|
|
||||||
smirks='[H:1][C:2]>>[H:1][O:2]'
|
|
||||||
)
|
|
||||||
|
|
||||||
result = rule.as_svg
|
result = rule.as_svg
|
||||||
mock_smirks_to_svg.assert_called_once_with(rule.smirks, True, width=800, height=400)
|
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):
|
def test_atomic_transaction(self):
|
||||||
"""Test that rule creation is atomic."""
|
"""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
|
# This should work normally
|
||||||
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
rule = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||||
self.assertIsInstance(rule, SimpleAmbitRule)
|
self.assertIsInstance(rule, SimpleAmbitRule)
|
||||||
|
|
||||||
# Test transaction rollback on error
|
# 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):
|
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
|
# Verify no partial data was saved
|
||||||
self.assertEqual(SimpleAmbitRule.objects.filter(package=self.package).count(), 1)
|
self.assertEqual(SimpleAmbitRule.objects.filter(package=self.package).count(), 1)
|
||||||
|
|
||||||
def test_multiple_duplicate_warning(self):
|
def test_multiple_duplicate_warning(self):
|
||||||
"""Test logging when multiple duplicates are found."""
|
"""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
|
# Create first rule
|
||||||
rule1 = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
rule1 = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||||
|
|
||||||
# Manually create a duplicate to simulate the error condition
|
# 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()
|
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
|
# This should find the existing rule and log an error about multiple matches
|
||||||
result = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
result = SimpleAmbitRule.create(package=self.package, smirks=smirks)
|
||||||
|
|
||||||
@@ -339,24 +312,28 @@ class SimpleAmbitRuleTest(TestCase):
|
|||||||
|
|
||||||
# Should log an error about multiple matches
|
# Should log an error about multiple matches
|
||||||
mock_logger.error.assert_called()
|
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):
|
def test_model_fields(self):
|
||||||
"""Test model field properties."""
|
"""Test model field properties."""
|
||||||
rule = SimpleAmbitRule.create(
|
rule = SimpleAmbitRule.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
smirks='[H:1][C:2]>>[H:1][O:2]',
|
smirks="[H:1][C:2]>>[H:1][O:2]",
|
||||||
reactant_filter_smarts='[CH3]',
|
reactant_filter_smarts="[CH3]",
|
||||||
product_filter_smarts='[OH]'
|
product_filter_smarts="[OH]",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test field properties
|
# Test field properties
|
||||||
self.assertFalse(rule._meta.get_field('smirks').blank)
|
self.assertFalse(rule._meta.get_field("smirks").blank)
|
||||||
self.assertFalse(rule._meta.get_field('smirks').null)
|
self.assertFalse(rule._meta.get_field("smirks").null)
|
||||||
self.assertTrue(rule._meta.get_field('reactant_filter_smarts').null)
|
self.assertTrue(rule._meta.get_field("reactant_filter_smarts").null)
|
||||||
self.assertTrue(rule._meta.get_field('product_filter_smarts').null)
|
self.assertTrue(rule._meta.get_field("product_filter_smarts").null)
|
||||||
|
|
||||||
# Test verbose names
|
# Test verbose names
|
||||||
self.assertEqual(rule._meta.get_field('smirks').verbose_name, 'SMIRKS')
|
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(
|
||||||
self.assertEqual(rule._meta.get_field('product_filter_smarts').verbose_name, 'Product Filter SMARTS')
|
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"
|
||||||
|
)
|
||||||
|
|||||||
+13
-16
@@ -1,32 +1,29 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from epdb.logic import SNode, SEdge, SPathway
|
from epdb.logic import SNode, SEdge
|
||||||
|
|
||||||
|
|
||||||
class SObjectTest(TestCase):
|
class SObjectTest(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_snode_eq(self):
|
def test_snode_eq(self):
|
||||||
snode1 = 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)
|
snode2 = SNode("CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O", 0)
|
||||||
assert snode1 == snode2
|
assert snode1 == snode2
|
||||||
|
|
||||||
def test_snode_hash(self):
|
def test_snode_hash(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_sedge_eq(self):
|
def test_sedge_eq(self):
|
||||||
sedge1 = SEdge([SNode('CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O', 0)],
|
sedge1 = SEdge(
|
||||||
[SNode('CN1C(=O)NC2=C(C1=O)N(C)C=N2', 1), SNode('C=O', 1)],
|
[SNode("CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O", 0)],
|
||||||
rule=None)
|
[SNode("CN1C(=O)NC2=C(C1=O)N(C)C=N2", 1), SNode("C=O", 1)],
|
||||||
sedge2 = SEdge([SNode('CN1C2C(N(C(N(C)C=2N=C1)=O)C)=O', 0)],
|
rule=None,
|
||||||
[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
|
assert sedge1 == sedge2
|
||||||
|
|
||||||
def test_sedge_hash(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_spathway(self):
|
|
||||||
pw = SPathway()
|
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
from django.test import TestCase, override_settings
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.conf import settings as s
|
||||||
|
|
||||||
|
from epdb.logic import UserManager
|
||||||
|
from epdb.models import Package, User
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models")
|
||||||
|
class PathwayViewTest(TestCase):
|
||||||
|
fixtures = ["test_fixtures_incl_model.jsonl.gz"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(PathwayViewTest, cls).setUpClass()
|
||||||
|
cls.user1 = UserManager.create_user(
|
||||||
|
"user1",
|
||||||
|
"user1@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
set_setting=True,
|
||||||
|
add_to_group=True,
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
cls.user1_default_package = cls.user1.default_package
|
||||||
|
cls.model_package = Package.objects.get(name="Fixtures")
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client.force_login(self.user1)
|
||||||
|
|
||||||
|
def test_predict(self):
|
||||||
|
self.client.force_login(User.objects.get(username="admin"))
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"package model detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.model_package.uuid),
|
||||||
|
"model_uuid": str(self.model_package.models.first().uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"classify": "ILikeCats!",
|
||||||
|
"smiles": "CCN(CC)C(=O)C1=CC(=CC=C1)CO",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"products": [["O=C(O)C1=CC(CO)=CC=C1", "CCNCC"]],
|
||||||
|
"probability": 0.25,
|
||||||
|
"btrule": {
|
||||||
|
"url": "http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/0e6e9290-b658-4450-b291-3ec19fa19206",
|
||||||
|
"name": "bt0430-4011",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"products": [["CCNC(=O)C1=CC(CO)=CC=C1", "CC=O"]],
|
||||||
|
"probability": 0.0,
|
||||||
|
"btrule": {
|
||||||
|
"url": "http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/27a3a353-0b66-4228-bd16-e407949e90df",
|
||||||
|
"name": "bt0243-4301",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"products": [["CCN(CC)C(=O)C1=CC(C=O)=CC=C1"]],
|
||||||
|
"probability": 0.75,
|
||||||
|
"btrule": {
|
||||||
|
"url": "http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/2f2e0c39-e109-4836-959f-2bda2524f022",
|
||||||
|
"name": "bt0001-3568",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
actual = response.json()
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"package model detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.model_package.uuid),
|
||||||
|
"model_uuid": str(self.model_package.models.first().uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"classify": "ILikeCats!",
|
||||||
|
"smiles": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertEqual(response.json()["error"], "Received empty SMILES")
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"package model detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.model_package.uuid),
|
||||||
|
"model_uuid": str(self.model_package.models.first().uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"classify": "ILikeCats!",
|
||||||
|
"smiles": " ", # Input should be stripped
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertEqual(response.json()["error"], "Received empty SMILES")
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"package model detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.model_package.uuid),
|
||||||
|
"model_uuid": str(self.model_package.models.first().uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"classify": "ILikeCats!",
|
||||||
|
"smiles": "RandomInput",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertEqual(response.json()["error"], '"RandomInput" is not a valid SMILES')
|
||||||
@@ -13,19 +13,34 @@ class PackageViewTest(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(PackageViewTest, cls).setUpClass()
|
super(PackageViewTest, cls).setUpClass()
|
||||||
cls.user1 = UserManager.create_user("user1", "user1@envipath.com", "SuperSafe",
|
cls.user1 = UserManager.create_user(
|
||||||
set_setting=False, add_to_group=True, is_active=True)
|
"user1",
|
||||||
cls.user2 = UserManager.create_user("user2", "user2@envipath.com", "SuperSafe",
|
"user1@envipath.com",
|
||||||
set_setting=False, add_to_group=True, is_active=True)
|
"SuperSafe",
|
||||||
|
set_setting=False,
|
||||||
|
add_to_group=True,
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
cls.user2 = UserManager.create_user(
|
||||||
|
"user2",
|
||||||
|
"user2@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
set_setting=False,
|
||||||
|
add_to_group=True,
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client.force_login(self.user1)
|
self.client.force_login(self.user1)
|
||||||
|
|
||||||
def test_create_package(self):
|
def test_create_package(self):
|
||||||
response = self.client.post(reverse("packages"), {
|
response = self.client.post(
|
||||||
"package-name": "Test Package",
|
reverse("packages"),
|
||||||
"package-description": "Just a Description",
|
{
|
||||||
})
|
"package-name": "Test Package",
|
||||||
|
"package-description": "Just a Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
package_url = response.url
|
package_url = response.url
|
||||||
|
|
||||||
@@ -41,13 +56,12 @@ class PackageViewTest(TestCase):
|
|||||||
file = SimpleUploadedFile(
|
file = SimpleUploadedFile(
|
||||||
"Fixture_Package.json",
|
"Fixture_Package.json",
|
||||||
open(s.FIXTURE_DIRS[0] / "Fixture_Package.json", "rb").read(),
|
open(s.FIXTURE_DIRS[0] / "Fixture_Package.json", "rb").read(),
|
||||||
content_type="application/json"
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self.client.post(reverse("packages"), {
|
response = self.client.post(
|
||||||
"file": file,
|
reverse("packages"), {"file": file, "hidden": "import-package-json"}
|
||||||
"hidden": "import-package-json"
|
)
|
||||||
})
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
package_url = response.url
|
package_url = response.url
|
||||||
@@ -67,13 +81,12 @@ class PackageViewTest(TestCase):
|
|||||||
file = SimpleUploadedFile(
|
file = SimpleUploadedFile(
|
||||||
"EAWAG-BBD.json",
|
"EAWAG-BBD.json",
|
||||||
open(s.FIXTURE_DIRS[0] / "packages" / "2025-07-18" / "EAWAG-BBD.json", "rb").read(),
|
open(s.FIXTURE_DIRS[0] / "packages" / "2025-07-18" / "EAWAG-BBD.json", "rb").read(),
|
||||||
content_type="application/json"
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self.client.post(reverse("packages"), {
|
response = self.client.post(
|
||||||
"file": file,
|
reverse("packages"), {"file": file, "hidden": "import-legacy-package-json"}
|
||||||
"hidden": "import-legacy-package-json"
|
)
|
||||||
})
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
package_url = response.url
|
package_url = response.url
|
||||||
@@ -90,17 +103,23 @@ class PackageViewTest(TestCase):
|
|||||||
self.assertEqual(upp.permission, Permission.ALL[0])
|
self.assertEqual(upp.permission, Permission.ALL[0])
|
||||||
|
|
||||||
def test_edit_package(self):
|
def test_edit_package(self):
|
||||||
response = self.client.post(reverse("packages"), {
|
response = self.client.post(
|
||||||
"package-name": "Test Package",
|
reverse("packages"),
|
||||||
"package-description": "Just a Description",
|
{
|
||||||
})
|
"package-name": "Test Package",
|
||||||
|
"package-description": "Just a Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
package_url = response.url
|
package_url = response.url
|
||||||
|
|
||||||
self.client.post(package_url, {
|
self.client.post(
|
||||||
"package-name": "New Name",
|
package_url,
|
||||||
"package-description": "New Description",
|
{
|
||||||
})
|
"package-name": "New Name",
|
||||||
|
"package-description": "New Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
p = Package.objects.get(url=package_url)
|
p = Package.objects.get(url=package_url)
|
||||||
|
|
||||||
@@ -108,10 +127,13 @@ class PackageViewTest(TestCase):
|
|||||||
self.assertEqual(p.description, "New Description")
|
self.assertEqual(p.description, "New Description")
|
||||||
|
|
||||||
def test_edit_package_permissions(self):
|
def test_edit_package_permissions(self):
|
||||||
response = self.client.post(reverse("packages"), {
|
response = self.client.post(
|
||||||
"package-name": "Test Package",
|
reverse("packages"),
|
||||||
"package-description": "Just a Description",
|
{
|
||||||
})
|
"package-name": "Test Package",
|
||||||
|
"package-description": "Just a Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
package_url = response.url
|
package_url = response.url
|
||||||
p = Package.objects.get(url=package_url)
|
p = Package.objects.get(url=package_url)
|
||||||
@@ -119,57 +141,63 @@ class PackageViewTest(TestCase):
|
|||||||
with self.assertRaises(UserPackagePermission.DoesNotExist):
|
with self.assertRaises(UserPackagePermission.DoesNotExist):
|
||||||
UserPackagePermission.objects.get(package=p, user=self.user2)
|
UserPackagePermission.objects.get(package=p, user=self.user2)
|
||||||
|
|
||||||
self.client.post(package_url, {
|
self.client.post(
|
||||||
"grantee": self.user2.url,
|
package_url,
|
||||||
"read": "on",
|
{
|
||||||
"write": "on",
|
"grantee": self.user2.url,
|
||||||
})
|
"read": "on",
|
||||||
|
"write": "on",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
upp = UserPackagePermission.objects.get(package=p, user=self.user2)
|
upp = UserPackagePermission.objects.get(package=p, user=self.user2)
|
||||||
|
|
||||||
self.assertEqual(upp.permission, Permission.WRITE[0])
|
self.assertEqual(upp.permission, Permission.WRITE[0])
|
||||||
|
|
||||||
def test_publish_package(self):
|
def test_publish_package(self):
|
||||||
response = self.client.post(reverse("packages"), {
|
response = self.client.post(
|
||||||
"package-name": "Test Package",
|
reverse("packages"),
|
||||||
"package-description": "Just a Description",
|
{
|
||||||
})
|
"package-name": "Test Package",
|
||||||
|
"package-description": "Just a Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
package_url = response.url
|
package_url = response.url
|
||||||
p = Package.objects.get(url=package_url)
|
p = Package.objects.get(url=package_url)
|
||||||
|
|
||||||
self.client.post(package_url, {
|
self.client.post(package_url, {"hidden": "publish-package"})
|
||||||
"hidden": "publish-package"
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertEqual(Group.objects.filter(public=True).count(), 1)
|
self.assertEqual(Group.objects.filter(public=True).count(), 1)
|
||||||
g = Group.objects.get(public=True)
|
g = Group.objects.get(public=True)
|
||||||
gpp = GroupPackagePermission.objects.get(package=p, group=g)
|
gpp = GroupPackagePermission.objects.get(package=p, group=g)
|
||||||
self.assertEqual(gpp.permission, Permission.READ[0])
|
self.assertEqual(gpp.permission, Permission.READ[0])
|
||||||
|
|
||||||
|
|
||||||
def test_set_package_license(self):
|
def test_set_package_license(self):
|
||||||
response = self.client.post(reverse("packages"), {
|
response = self.client.post(
|
||||||
"package-name": "Test Package",
|
reverse("packages"),
|
||||||
"package-description": "Just a Description",
|
{
|
||||||
})
|
"package-name": "Test Package",
|
||||||
|
"package-description": "Just a Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
package_url = response.url
|
package_url = response.url
|
||||||
p = Package.objects.get(url=package_url)
|
p = Package.objects.get(url=package_url)
|
||||||
|
|
||||||
self.client.post(package_url, {
|
self.client.post(package_url, {"license": "no-license"})
|
||||||
"license": "no-license"
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertIsNone(p.license)
|
self.assertIsNone(p.license)
|
||||||
# TODO test others
|
# TODO test others
|
||||||
|
|
||||||
|
|
||||||
def test_delete_package(self):
|
def test_delete_package(self):
|
||||||
response = self.client.post(reverse("packages"), {
|
response = self.client.post(
|
||||||
"package-name": "Test Package",
|
reverse("packages"),
|
||||||
"package-description": "Just a Description",
|
{
|
||||||
})
|
"package-name": "Test Package",
|
||||||
|
"package-description": "Just a Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
package_url = response.url
|
package_url = response.url
|
||||||
p = Package.objects.get(url=package_url)
|
p = Package.objects.get(url=package_url)
|
||||||
@@ -178,3 +206,15 @@ class PackageViewTest(TestCase):
|
|||||||
|
|
||||||
response = self.client.get(package_url)
|
response = self.client.get(package_url)
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_delete_default_package(self):
|
||||||
|
self.client.force_login(self.user1)
|
||||||
|
# Try to delete the default package
|
||||||
|
response = self.client.post(self.user1.default_package.url, {"hidden": "delete"})
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertTrue(
|
||||||
|
"You cannot delete the default package. "
|
||||||
|
"If you want to delete this package you have to "
|
||||||
|
"set another default package first" in response.content.decode()
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
from django.test import TestCase, override_settings
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.conf import settings as s
|
||||||
|
|
||||||
|
from epdb.logic import UserManager, PackageManager
|
||||||
|
from epdb.models import Pathway, Edge
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models")
|
||||||
|
class PathwayViewTest(TestCase):
|
||||||
|
fixtures = ["test_fixtures_incl_model.jsonl.gz"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(PathwayViewTest, cls).setUpClass()
|
||||||
|
cls.user1 = UserManager.create_user(
|
||||||
|
"user1",
|
||||||
|
"user1@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
set_setting=True,
|
||||||
|
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_predict_pathway(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("pathways"),
|
||||||
|
{
|
||||||
|
"name": "Test Pathway",
|
||||||
|
"description": "Just a Description",
|
||||||
|
"predict": "predict",
|
||||||
|
"smiles": "CCN(CC)C(=O)C1=CC(=CC=C1)CO",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
pathway_url = response.url
|
||||||
|
|
||||||
|
pw = Pathway.objects.get(url=pathway_url)
|
||||||
|
self.assertEqual(self.user1_default_package, pw.package)
|
||||||
|
|
||||||
|
self.assertEqual(pw.name, "Test Pathway")
|
||||||
|
self.assertEqual(pw.description, "Just a Description")
|
||||||
|
self.assertEqual(len(pw.root_nodes), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
pw.root_nodes.first().default_node_label.smiles, "CCN(CC)C(=O)C1=CC(CO)=CC=C1"
|
||||||
|
)
|
||||||
|
|
||||||
|
first_level_nodes = {
|
||||||
|
# Edge 1
|
||||||
|
"CCN(CC)C(=O)C1=CC(C=O)=CC=C1",
|
||||||
|
# Edge 2
|
||||||
|
"CCNC(=O)C1=CC(CO)=CC=C1",
|
||||||
|
"CC=O",
|
||||||
|
# Edge 3
|
||||||
|
"CCNCC",
|
||||||
|
"O=C(O)C1=CC(CO)=CC=C1",
|
||||||
|
}
|
||||||
|
|
||||||
|
predicted_nodes = set()
|
||||||
|
edges = Edge.objects.filter(start_nodes__in=[pw.root_nodes.first()])
|
||||||
|
|
||||||
|
for edge in edges:
|
||||||
|
for n in edge.end_nodes.all():
|
||||||
|
predicted_nodes.add(n.default_node_label.smiles)
|
||||||
|
|
||||||
|
self.assertEqual(first_level_nodes, predicted_nodes)
|
||||||
|
|
||||||
|
def test_predict_package_pathway(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("package pathway list", kwargs={"package_uuid": str(self.package.uuid)}),
|
||||||
|
{
|
||||||
|
"name": "Test Pathway",
|
||||||
|
"description": "Just a Description",
|
||||||
|
"predict": "predict",
|
||||||
|
"smiles": "CCN(CC)C(=O)C1=CC(=CC=C1)CO",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
pathway_url = response.url
|
||||||
|
|
||||||
|
pw = Pathway.objects.get(url=pathway_url)
|
||||||
|
self.assertEqual(self.package, pw.package)
|
||||||
|
|
||||||
|
self.assertEqual(pw.name, "Test Pathway")
|
||||||
|
self.assertEqual(pw.description, "Just a Description")
|
||||||
|
self.assertEqual(len(pw.root_nodes), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
pw.root_nodes.first().default_node_label.smiles, "CCN(CC)C(=O)C1=CC(CO)=CC=C1"
|
||||||
|
)
|
||||||
|
|
||||||
|
first_level_nodes = {
|
||||||
|
# Edge 1
|
||||||
|
"CCN(CC)C(=O)C1=CC(C=O)=CC=C1",
|
||||||
|
# Edge 2
|
||||||
|
"CCNC(=O)C1=CC(CO)=CC=C1",
|
||||||
|
"CC=O",
|
||||||
|
# Edge 3
|
||||||
|
"CCNCC",
|
||||||
|
"O=C(O)C1=CC(CO)=CC=C1",
|
||||||
|
}
|
||||||
|
|
||||||
|
predicted_nodes = set()
|
||||||
|
edges = Edge.objects.filter(start_nodes__in=[pw.root_nodes.first()])
|
||||||
|
|
||||||
|
for edge in edges:
|
||||||
|
for n in edge.end_nodes.all():
|
||||||
|
predicted_nodes.add(n.default_node_label.smiles)
|
||||||
|
|
||||||
|
self.assertEqual(first_level_nodes, predicted_nodes)
|
||||||
|
|
||||||
|
def test_set_aliases(self):
|
||||||
|
alias_1 = "Alias 1"
|
||||||
|
alias_2 = "Alias 2"
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("package pathway list", kwargs={"package_uuid": str(self.package.uuid)}),
|
||||||
|
{
|
||||||
|
"name": "Test Pathway",
|
||||||
|
"description": "Just a Description",
|
||||||
|
"predict": "predict",
|
||||||
|
"smiles": "CCN(CC)C(=O)C1=CC(=CC=C1)CO",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
pathway_url = response.url
|
||||||
|
pw = Pathway.objects.get(url=pathway_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package pathway detail",
|
||||||
|
kwargs={"package_uuid": str(pw.package.uuid), "pathway_uuid": str(pw.uuid)},
|
||||||
|
),
|
||||||
|
{"aliases": [alias_1, alias_2]},
|
||||||
|
)
|
||||||
|
|
||||||
|
pw = Pathway.objects.get(url=pathway_url)
|
||||||
|
self.assertEqual(len(pw.aliases), 2)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package pathway detail",
|
||||||
|
kwargs={"package_uuid": str(pw.package.uuid), "pathway_uuid": str(pw.uuid)},
|
||||||
|
),
|
||||||
|
{"aliases": [alias_1]},
|
||||||
|
)
|
||||||
|
|
||||||
|
pw = Pathway.objects.get(url=pathway_url)
|
||||||
|
self.assertEqual(len(pw.aliases), 1)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package pathway detail",
|
||||||
|
kwargs={"package_uuid": str(pw.package.uuid), "pathway_uuid": str(pw.uuid)},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
# We have to set an empty string to avoid that the parameter is removed
|
||||||
|
"aliases": ""
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pw = Pathway.objects.get(url=pathway_url)
|
||||||
|
self.assertEqual(len(pw.aliases), 0)
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
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 Reaction, Scenario, ExternalDatabase
|
||||||
|
|
||||||
|
|
||||||
|
class ReactionViewTest(TestCase):
|
||||||
|
fixtures = ["test_fixtures.jsonl.gz"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(ReactionViewTest, 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_reaction(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("reactions"),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0001",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0001",
|
||||||
|
"reaction-smirks": "C(CCl)Cl>>C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
reaction_url = response.url
|
||||||
|
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
|
||||||
|
self.assertEqual(r.package, self.user1_default_package)
|
||||||
|
self.assertEqual(r.name, "Eawag BBD reaction r0001")
|
||||||
|
self.assertEqual(r.description, "Description for Eawag BBD reaction r0001")
|
||||||
|
self.assertEqual(r.smirks(), "C(CCl)Cl>>C(CO)Cl")
|
||||||
|
self.assertEqual(self.user1_default_package.reactions.count(), 1)
|
||||||
|
|
||||||
|
# Adding the same rule again should return the existing one, hence not increasing the number of rules
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("reactions"),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0001",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0001",
|
||||||
|
"reaction-smirks": "C(CCl)Cl>>C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.url, reaction_url)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(self.user1_default_package.reactions.count(), 1)
|
||||||
|
|
||||||
|
# Adding the same rule in a different package should create a new rule
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("package reaction list", kwargs={"package_uuid": self.package.uuid}),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0001",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0001",
|
||||||
|
"reaction-smirks": "C(CCl)Cl>>C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertNotEqual(reaction_url, response.url)
|
||||||
|
|
||||||
|
# adding another reaction should increase count
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("reactions"),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0002",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0002",
|
||||||
|
"reaction-smirks": "C(CO)Cl>>C(C=O)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(self.user1_default_package.reactions.count(), 2)
|
||||||
|
|
||||||
|
# Edit
|
||||||
|
def test_edit_rule(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("reactions"),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0001",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0001",
|
||||||
|
"reaction-smirks": "C(CCl)Cl>>C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
reaction_url = response.url
|
||||||
|
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.user1_default_package.uuid),
|
||||||
|
"reaction_uuid": str(r.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"reaction-name": "Test Reaction Adjusted",
|
||||||
|
"reaction-description": "New Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
self.assertEqual(r.name, "Test Reaction Adjusted")
|
||||||
|
self.assertEqual(r.description, "New Description")
|
||||||
|
|
||||||
|
# Rest stays untouched
|
||||||
|
self.assertEqual(r.smirks(), "C(CCl)Cl>>C(CO)Cl")
|
||||||
|
self.assertEqual(self.user1_default_package.reactions.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("reactions"),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0001",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0001",
|
||||||
|
"reaction-smirks": "C(CCl)Cl>>C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
reaction_url = response.url
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "reaction_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"selected-scenarios": [s1.url, s2.url]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(r.scenarios.all()), 2)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "reaction_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"selected-scenarios": [s1.url]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(r.scenarios.all()), 1)
|
||||||
|
self.assertEqual(r.scenarios.first().url, s1.url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "reaction_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
# We have to set an empty string to avoid that the parameter is removed
|
||||||
|
"selected-scenarios": ""
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(r.scenarios.all()), 0)
|
||||||
|
|
||||||
|
def test_copy(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("reactions"),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0001",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0001",
|
||||||
|
"reaction-smirks": "C(CCl)Cl>>C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
reaction_url = response.url
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.package.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"hidden": "copy", "object_to_copy": r.url},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
copied_object_url = response.json()["success"]
|
||||||
|
copied_reaction = Reaction.objects.get(url=copied_object_url)
|
||||||
|
|
||||||
|
self.assertEqual(copied_reaction.name, r.name)
|
||||||
|
self.assertEqual(copied_reaction.description, r.description)
|
||||||
|
self.assertEqual(copied_reaction.smirks(), r.smirks())
|
||||||
|
|
||||||
|
# Copy to the same package should fail
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(r.package.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"hidden": "copy", "object_to_copy": r.url},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertEqual(
|
||||||
|
response.json()["error"], f"Can't copy object {reaction_url} to the same package!"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_references(self):
|
||||||
|
ext_db, _ = ExternalDatabase.objects.get_or_create(
|
||||||
|
name="KEGG Reaction",
|
||||||
|
defaults={
|
||||||
|
"full_name": "KEGG Reaction Database",
|
||||||
|
"description": "Database of biochemical reactions",
|
||||||
|
"base_url": "https://www.genome.jp",
|
||||||
|
"url_pattern": "https://www.genome.jp/entry/{id}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ext_db2, _ = ExternalDatabase.objects.get_or_create(
|
||||||
|
name="RHEA",
|
||||||
|
defaults={
|
||||||
|
"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}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("reactions"),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0001",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0001",
|
||||||
|
"reaction-smirks": "C(CCl)Cl>>C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
reaction_url = response.url
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(r.package.uuid),
|
||||||
|
"reaction_uuid": str(r.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"selected-database": ext_db.pk, "identifier": "C12345"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(r.external_identifiers.count(), 1)
|
||||||
|
self.assertEqual(r.external_identifiers.first().database, ext_db)
|
||||||
|
self.assertEqual(r.external_identifiers.first().identifier_value, "C12345")
|
||||||
|
# TODO Fixture contains old url template there the real test fails, use old value instead
|
||||||
|
# self.assertEqual(r.external_identifiers.first().url, 'https://www.genome.jp/entry/C12345')
|
||||||
|
self.assertEqual(
|
||||||
|
r.external_identifiers.first().url, "https://www.genome.jp/entry/reaction+C12345"
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(r.package.uuid),
|
||||||
|
"reaction_uuid": str(r.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"selected-database": ext_db2.pk, "identifier": "60116"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(r.external_identifiers.count(), 2)
|
||||||
|
self.assertEqual(r.external_identifiers.last().database, ext_db2)
|
||||||
|
self.assertEqual(r.external_identifiers.last().identifier_value, "60116")
|
||||||
|
self.assertEqual(r.external_identifiers.last().url, "https://www.rhea-db.org/rhea/60116")
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("reactions"),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0001",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0001",
|
||||||
|
"reaction-smirks": "C(CCl)Cl>>C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
reaction_url = response.url
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "reaction_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"hidden": "delete"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(self.user1_default_package.reactions.count(), 0)
|
||||||
|
|
||||||
|
def test_set_aliases(self):
|
||||||
|
alias_1 = "Alias 1"
|
||||||
|
alias_2 = "Alias 2"
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("reactions"),
|
||||||
|
{
|
||||||
|
"reaction-name": "Eawag BBD reaction r0001",
|
||||||
|
"reaction-description": "Description for Eawag BBD reaction r0001",
|
||||||
|
"reaction-smirks": "C(CCl)Cl>>C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
reaction_url = response.url
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "reaction_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"aliases": [alias_1, alias_2]},
|
||||||
|
)
|
||||||
|
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
self.assertEqual(len(r.aliases), 2)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "reaction_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"aliases": [alias_1]},
|
||||||
|
)
|
||||||
|
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
self.assertEqual(len(r.aliases), 1)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package reaction detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "reaction_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
# We have to set an empty string to avoid that the parameter is removed
|
||||||
|
"aliases": ""
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
r = Reaction.objects.get(url=reaction_url)
|
||||||
|
self.assertEqual(len(r.aliases), 0)
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
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 Rule, Scenario
|
||||||
|
|
||||||
|
|
||||||
|
class RuleViewTest(TestCase):
|
||||||
|
fixtures = ["test_fixtures.jsonl.gz"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(RuleViewTest, 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_rule(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("rules"),
|
||||||
|
{
|
||||||
|
"rule-name": "Test Rule",
|
||||||
|
"rule-description": "Just a Description",
|
||||||
|
"rule-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",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
rule_url = response.url
|
||||||
|
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
|
||||||
|
self.assertEqual(r.package, self.user1_default_package)
|
||||||
|
self.assertEqual(r.name, "Test Rule")
|
||||||
|
self.assertEqual(r.description, "Just a Description")
|
||||||
|
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(self.user1_default_package.rules.count(), 1)
|
||||||
|
|
||||||
|
# Adding the same rule again should return the existing one, hence not increasing the number of rules
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("rules"),
|
||||||
|
{
|
||||||
|
"rule-name": "Test Rule",
|
||||||
|
"rule-description": "Just a Description",
|
||||||
|
"rule-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",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.url, rule_url)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(self.user1_default_package.rules.count(), 1)
|
||||||
|
|
||||||
|
# Adding the same rule in a different package should create a new rule
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("package rule list", kwargs={"package_uuid": self.package.uuid}),
|
||||||
|
{
|
||||||
|
"rule-name": "Test Rule",
|
||||||
|
"rule-description": "Just a Description",
|
||||||
|
"rule-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",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertNotEqual(rule_url, response.url)
|
||||||
|
|
||||||
|
# Edit
|
||||||
|
def test_edit_rule(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("rules"),
|
||||||
|
{
|
||||||
|
"rule-name": "Test Rule",
|
||||||
|
"rule-description": "Just a Description",
|
||||||
|
"rule-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",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
rule_url = response.url
|
||||||
|
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package rule detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.user1_default_package.uuid),
|
||||||
|
"rule_uuid": str(r.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"rule-name": "Test Rule Adjusted",
|
||||||
|
"rule-description": "New Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
self.assertEqual(r.name, "Test Rule Adjusted")
|
||||||
|
self.assertEqual(r.description, "New Description")
|
||||||
|
|
||||||
|
# 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("rules"),
|
||||||
|
{
|
||||||
|
"rule-name": "Test Rule",
|
||||||
|
"rule-description": "Just a Description",
|
||||||
|
"rule-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",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
rule_url = response.url
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package rule detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "rule_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"selected-scenarios": [s1.url, s2.url]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(r.scenarios.all()), 2)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package rule detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "rule_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"selected-scenarios": [s1.url]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(r.scenarios.all()), 1)
|
||||||
|
self.assertEqual(r.scenarios.first().url, s1.url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package rule detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "rule_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
# We have to set an empty string to avoid that the parameter is removed
|
||||||
|
"selected-scenarios": ""
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(r.scenarios.all()), 0)
|
||||||
|
|
||||||
|
def test_copy(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("rules"),
|
||||||
|
{
|
||||||
|
"rule-name": "Test Rule",
|
||||||
|
"rule-description": "Just a Description",
|
||||||
|
"rule-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",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
rule_url = response.url
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.package.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"hidden": "copy", "object_to_copy": r.url},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
copied_object_url = response.json()["success"]
|
||||||
|
copied_rule = Rule.objects.get(url=copied_object_url)
|
||||||
|
|
||||||
|
self.assertEqual(copied_rule.name, r.name)
|
||||||
|
self.assertEqual(copied_rule.description, r.description)
|
||||||
|
self.assertEqual(copied_rule.smirks, r.smirks)
|
||||||
|
|
||||||
|
# Copy to the same package should fail
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(r.package.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"hidden": "copy", "object_to_copy": r.url},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertEqual(
|
||||||
|
response.json()["error"], f"Can't copy object {rule_url} to the same package!"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("rules"),
|
||||||
|
{
|
||||||
|
"rule-name": "Test Rule",
|
||||||
|
"rule-description": "Just a Description",
|
||||||
|
"rule-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",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
rule_url = response.url
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package rule detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "rule_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"hidden": "delete"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(self.user1_default_package.rules.count(), 0)
|
||||||
|
|
||||||
|
def test_set_aliases(self):
|
||||||
|
alias_1 = "Alias 1"
|
||||||
|
alias_2 = "Alias 2"
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("rules"),
|
||||||
|
{
|
||||||
|
"rule-name": "Test Rule",
|
||||||
|
"rule-description": "Just a Description",
|
||||||
|
"rule-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",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
rule_url = response.url
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package rule detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "rule_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"aliases": [alias_1, alias_2]},
|
||||||
|
)
|
||||||
|
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
self.assertEqual(len(r.aliases), 2)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package rule detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "rule_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{"aliases": [alias_1]},
|
||||||
|
)
|
||||||
|
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
self.assertEqual(len(r.aliases), 1)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package rule detail",
|
||||||
|
kwargs={"package_uuid": str(r.package.uuid), "rule_uuid": str(r.uuid)},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
# We have to set an empty string to avoid that the parameter is removed
|
||||||
|
"aliases": ""
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
r = Rule.objects.get(url=rule_url)
|
||||||
|
self.assertEqual(len(r.aliases), 0)
|
||||||
@@ -11,70 +11,81 @@ class UserViewTest(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(UserViewTest, cls).setUpClass()
|
super(UserViewTest, cls).setUpClass()
|
||||||
cls.user = User.objects.get(username='anonymous')
|
cls.user = User.objects.get(username="anonymous")
|
||||||
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
|
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||||
cls.BBD_SUBSET = Package.objects.get(name='Fixtures')
|
cls.BBD_SUBSET = Package.objects.get(name="Fixtures")
|
||||||
|
|
||||||
def test_login_with_valid_credentials(self):
|
def test_login_with_valid_credentials(self):
|
||||||
response = self.client.post(reverse("login"), {
|
response = self.client.post(
|
||||||
"username": "user0",
|
reverse("login"),
|
||||||
"password": 'SuperSafe',
|
{
|
||||||
})
|
"username": "user0",
|
||||||
|
"password": "SuperSafe",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertRedirects(response, reverse("index"))
|
self.assertRedirects(response, reverse("index"))
|
||||||
self.assertTrue(response.wsgi_request.user.is_authenticated)
|
self.assertTrue(response.wsgi_request.user.is_authenticated)
|
||||||
|
|
||||||
def test_login_with_invalid_credentials(self):
|
def test_login_with_invalid_credentials(self):
|
||||||
response = self.client.post(reverse("login"), {
|
response = self.client.post(
|
||||||
"username": "user0",
|
reverse("login"),
|
||||||
"password": "wrongpassword",
|
{
|
||||||
})
|
"username": "user0",
|
||||||
|
"password": "wrongpassword",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertFalse(response.wsgi_request.user.is_authenticated)
|
self.assertFalse(response.wsgi_request.user.is_authenticated)
|
||||||
|
|
||||||
def test_register(self):
|
def test_register(self):
|
||||||
response = self.client.post(reverse("register"), {
|
response = self.client.post(
|
||||||
"username": "user1",
|
reverse("register"),
|
||||||
"email": "user1@envipath.com",
|
{
|
||||||
"password": "SuperSafe",
|
"username": "user1",
|
||||||
"rpassword": "SuperSafe",
|
"email": "user1@envipath.com",
|
||||||
})
|
"password": "SuperSafe",
|
||||||
|
"rpassword": "SuperSafe",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
# TODO currently fails as the fixture does not provide a global setting...
|
# TODO currently fails as the fixture does not provide a global setting...
|
||||||
self.assertContains(response, "Registration failed!")
|
self.assertContains(response, "Registration failed!")
|
||||||
|
|
||||||
def test_register_password_mismatch(self):
|
def test_register_password_mismatch(self):
|
||||||
response = self.client.post(reverse("register"), {
|
response = self.client.post(
|
||||||
"username": "user1",
|
reverse("register"),
|
||||||
"email": "user1@envipath.com",
|
{
|
||||||
"password": "SuperSafe",
|
"username": "user1",
|
||||||
"rpassword": "SuperSaf3",
|
"email": "user1@envipath.com",
|
||||||
})
|
"password": "SuperSafe",
|
||||||
|
"rpassword": "SuperSaf3",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, "Registration failed, provided passwords differ")
|
self.assertContains(response, "Registration failed, provided passwords differ")
|
||||||
|
|
||||||
def test_logout(self):
|
def test_logout(self):
|
||||||
response = self.client.post(reverse("login"), {
|
response = self.client.post(
|
||||||
"username": "user0",
|
reverse("login"), {"username": "user0", "password": "SuperSafe", "login": "true"}
|
||||||
"password": 'SuperSafe',
|
)
|
||||||
"login": "true"
|
|
||||||
})
|
|
||||||
self.assertTrue(response.wsgi_request.user.is_authenticated)
|
self.assertTrue(response.wsgi_request.user.is_authenticated)
|
||||||
|
|
||||||
response = self.client.post(reverse('logout'), {
|
response = self.client.post(
|
||||||
"logout": "true",
|
reverse("logout"),
|
||||||
})
|
{
|
||||||
|
"logout": "true",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertFalse(response.wsgi_request.user.is_authenticated)
|
self.assertFalse(response.wsgi_request.user.is_authenticated)
|
||||||
|
|
||||||
def test_next_param_properly_handled(self):
|
def test_next_param_properly_handled(self):
|
||||||
response = self.client.get(reverse('packages'))
|
response = self.client.get(reverse("packages"))
|
||||||
|
|
||||||
self.assertRedirects(response, f"{reverse('login')}/?next=/package")
|
self.assertRedirects(response, f"{reverse('login')}/?next=/package")
|
||||||
|
|
||||||
response = self.client.post(reverse('login'), {
|
response = self.client.post(
|
||||||
"username": "user0",
|
reverse("login"),
|
||||||
"password": 'SuperSafe',
|
{"username": "user0", "password": "SuperSafe", "login": "true", "next": "/package"},
|
||||||
"login": "true",
|
)
|
||||||
"next": "/package"
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertRedirects(response, reverse('packages'))
|
self.assertRedirects(response, reverse("packages"))
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import abc
|
|
||||||
from enviPy.epdb import Pathway
|
|
||||||
|
|
||||||
class PredictionSchema(abc.ABC):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class DFS(PredictionSchema):
|
|
||||||
|
|
||||||
def __init__(self, pw: Pathway, settings=None):
|
|
||||||
self.setting = settings or pw.prediction_settings
|
|
||||||
|
|
||||||
def predict(self):
|
|
||||||
pass
|
|
||||||
+130
-108
@@ -2,12 +2,11 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import List, Optional, Dict
|
from typing import List, Optional, Dict, TYPE_CHECKING
|
||||||
|
|
||||||
from indigo import Indigo, IndigoException, IndigoObject
|
from indigo import Indigo, IndigoException, IndigoObject
|
||||||
from indigo.renderer import IndigoRenderer
|
from indigo.renderer import IndigoRenderer
|
||||||
from rdkit import Chem
|
from rdkit import Chem, rdBase
|
||||||
from rdkit import RDLogger
|
|
||||||
from rdkit.Chem import MACCSkeys, Descriptors
|
from rdkit.Chem import MACCSkeys, Descriptors
|
||||||
from rdkit.Chem import rdChemReactions
|
from rdkit.Chem import rdChemReactions
|
||||||
from rdkit.Chem.Draw import rdMolDraw2D
|
from rdkit.Chem.Draw import rdMolDraw2D
|
||||||
@@ -15,9 +14,11 @@ from rdkit.Chem.MolStandardize import rdMolStandardize
|
|||||||
from rdkit.Chem.rdmolops import GetMolFrags
|
from rdkit.Chem.rdmolops import GetMolFrags
|
||||||
from rdkit.Contrib.IFG import ifg
|
from rdkit.Contrib.IFG import ifg
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
if TYPE_CHECKING:
|
||||||
RDLogger.DisableLog('rdApp.*')
|
from epdb.models import Rule
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
rdBase.DisableLog("rdApp.*")
|
||||||
|
|
||||||
# from rdkit import rdBase
|
# from rdkit import rdBase
|
||||||
# rdBase.LogToPythonLogger()
|
# rdBase.LogToPythonLogger()
|
||||||
@@ -28,7 +29,6 @@ RDLogger.DisableLog('rdApp.*')
|
|||||||
|
|
||||||
|
|
||||||
class ProductSet(object):
|
class ProductSet(object):
|
||||||
|
|
||||||
def __init__(self, product_set: List[str]):
|
def __init__(self, product_set: List[str]):
|
||||||
self.product_set = product_set
|
self.product_set = product_set
|
||||||
|
|
||||||
@@ -42,15 +42,18 @@ class ProductSet(object):
|
|||||||
return iter(self.product_set)
|
return iter(self.product_set)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return isinstance(other, ProductSet) and sorted(self.product_set) == sorted(other.product_set)
|
return isinstance(other, ProductSet) and sorted(self.product_set) == sorted(
|
||||||
|
other.product_set
|
||||||
|
)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash('-'.join(sorted(self.product_set)))
|
return hash("-".join(sorted(self.product_set)))
|
||||||
|
|
||||||
|
|
||||||
class PredictionResult(object):
|
class PredictionResult(object):
|
||||||
|
def __init__(
|
||||||
def __init__(self, product_sets: List['ProductSet'], probability: float, rule: Optional['Rule'] = None):
|
self, product_sets: List["ProductSet"], probability: float, rule: Optional["Rule"] = None
|
||||||
|
):
|
||||||
self.product_sets = product_sets
|
self.product_sets = product_sets
|
||||||
self.probability = probability
|
self.probability = probability
|
||||||
self.rule = rule
|
self.rule = rule
|
||||||
@@ -66,7 +69,6 @@ class PredictionResult(object):
|
|||||||
|
|
||||||
|
|
||||||
class FormatConverter(object):
|
class FormatConverter(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mass(smiles):
|
def mass(smiles):
|
||||||
return Descriptors.MolWt(FormatConverter.from_smiles(smiles))
|
return Descriptors.MolWt(FormatConverter.from_smiles(smiles))
|
||||||
@@ -127,7 +129,7 @@ class FormatConverter(object):
|
|||||||
if kekulize:
|
if kekulize:
|
||||||
try:
|
try:
|
||||||
mol = Chem.Kekulize(mol)
|
mol = Chem.Kekulize(mol)
|
||||||
except:
|
except Exception:
|
||||||
mol = Chem.Mol(mol.ToBinary())
|
mol = Chem.Mol(mol.ToBinary())
|
||||||
|
|
||||||
if not mol.GetNumConformers():
|
if not mol.GetNumConformers():
|
||||||
@@ -139,8 +141,8 @@ class FormatConverter(object):
|
|||||||
opts.clearBackground = False
|
opts.clearBackground = False
|
||||||
drawer.DrawMolecule(mol)
|
drawer.DrawMolecule(mol)
|
||||||
drawer.FinishDrawing()
|
drawer.FinishDrawing()
|
||||||
svg = drawer.GetDrawingText().replace('svg:', '')
|
svg = drawer.GetDrawingText().replace("svg:", "")
|
||||||
svg = re.sub("<\?xml.*\?>", '', svg)
|
svg = re.sub("<\?xml.*\?>", "", svg)
|
||||||
|
|
||||||
return svg
|
return svg
|
||||||
|
|
||||||
@@ -151,7 +153,7 @@ class FormatConverter(object):
|
|||||||
if kekulize:
|
if kekulize:
|
||||||
try:
|
try:
|
||||||
Chem.Kekulize(mol)
|
Chem.Kekulize(mol)
|
||||||
except:
|
except Exception:
|
||||||
mc = Chem.Mol(mol.ToBinary())
|
mc = Chem.Mol(mol.ToBinary())
|
||||||
|
|
||||||
if not mc.GetNumConformers():
|
if not mc.GetNumConformers():
|
||||||
@@ -178,7 +180,7 @@ class FormatConverter(object):
|
|||||||
smiles = tmp_smiles
|
smiles = tmp_smiles
|
||||||
|
|
||||||
if change is False:
|
if change is False:
|
||||||
print(f"nothing changed")
|
print("nothing changed")
|
||||||
|
|
||||||
return smiles
|
return smiles
|
||||||
|
|
||||||
@@ -198,7 +200,9 @@ class FormatConverter(object):
|
|||||||
parent_clean_mol = rdMolStandardize.FragmentParent(clean_mol)
|
parent_clean_mol = rdMolStandardize.FragmentParent(clean_mol)
|
||||||
|
|
||||||
# try to neutralize molecule
|
# try to neutralize molecule
|
||||||
uncharger = rdMolStandardize.Uncharger() # annoying, but necessary as no convenience method exists
|
uncharger = (
|
||||||
|
rdMolStandardize.Uncharger()
|
||||||
|
) # annoying, but necessary as no convenience method exists
|
||||||
uncharged_parent_clean_mol = uncharger.uncharge(parent_clean_mol)
|
uncharged_parent_clean_mol = uncharger.uncharge(parent_clean_mol)
|
||||||
|
|
||||||
# note that no attempt is made at reionization at this step
|
# note that no attempt is made at reionization at this step
|
||||||
@@ -239,17 +243,24 @@ class FormatConverter(object):
|
|||||||
try:
|
try:
|
||||||
rdChemReactions.ReactionFromSmarts(smirks)
|
rdChemReactions.ReactionFromSmarts(smirks)
|
||||||
return True
|
return True
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def apply(smiles: str, smirks: str, preprocess_smiles: bool = True, bracketize: bool = True,
|
def apply(
|
||||||
standardize: bool = True, kekulize: bool = True) -> List['ProductSet']:
|
smiles: str,
|
||||||
logger.debug(f'Applying {smirks} on {smiles}')
|
smirks: str,
|
||||||
|
preprocess_smiles: bool = True,
|
||||||
|
bracketize: bool = True,
|
||||||
|
standardize: bool = True,
|
||||||
|
kekulize: bool = True,
|
||||||
|
remove_stereo: bool = True,
|
||||||
|
) -> List["ProductSet"]:
|
||||||
|
logger.debug(f"Applying {smirks} on {smiles}")
|
||||||
|
|
||||||
# If explicitly wanted or rule generates multiple products add brackets around products to capture all
|
# If explicitly wanted or rule generates multiple products add brackets around products to capture all
|
||||||
if bracketize: # or "." in smirks:
|
if bracketize: # or "." in smirks:
|
||||||
smirks = smirks.split('>>')[0] + ">>(" + smirks.split('>>')[1] + ")"
|
smirks = smirks.split(">>")[0] + ">>(" + smirks.split(">>")[1] + ")"
|
||||||
|
|
||||||
# List of ProductSet objects
|
# List of ProductSet objects
|
||||||
pss = set()
|
pss = set()
|
||||||
@@ -274,7 +285,9 @@ class FormatConverter(object):
|
|||||||
Chem.SanitizeMol(product)
|
Chem.SanitizeMol(product)
|
||||||
product = GetMolFrags(product, asMols=True)
|
product = GetMolFrags(product, asMols=True)
|
||||||
for p in product:
|
for p in product:
|
||||||
p = FormatConverter.standardize(Chem.MolToSmiles(p))
|
p = FormatConverter.standardize(
|
||||||
|
Chem.MolToSmiles(p), remove_stereo=remove_stereo
|
||||||
|
)
|
||||||
prods.append(p)
|
prods.append(p)
|
||||||
|
|
||||||
# if kekulize:
|
# if kekulize:
|
||||||
@@ -300,9 +313,8 @@ class FormatConverter(object):
|
|||||||
# # bond.SetIsAromatic(False)
|
# # bond.SetIsAromatic(False)
|
||||||
# Chem.Kekulize(product)
|
# Chem.Kekulize(product)
|
||||||
|
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.error(f'Sanitizing and converting failed:\n{e}')
|
logger.error(f"Sanitizing and converting failed:\n{e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(prods):
|
if len(prods):
|
||||||
@@ -310,34 +322,14 @@ class FormatConverter(object):
|
|||||||
pss.add(ps)
|
pss.add(ps)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Applying {smirks} on {smiles} failed:\n{e}')
|
logger.error(f"Applying {smirks} on {smiles} failed:\n{e}")
|
||||||
|
|
||||||
return pss
|
return pss
|
||||||
|
|
||||||
# @staticmethod
|
|
||||||
# def apply(reaction, smiles):
|
|
||||||
# rxn = AllChem.ReactionFromSmarts(reaction)
|
|
||||||
# return [Chem.MolToSmiles(x, 1) for x in rxn.RunReactants((Chem.MolFromSmiles(smiles),))[0]]
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def MACCS(smiles):
|
def MACCS(smiles):
|
||||||
return MACCSkeys.GenMACCSKeys(FormatConverter.from_smiles(smiles))
|
return MACCSkeys.GenMACCSKeys(FormatConverter.from_smiles(smiles))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def neutralize_atoms(mol):
|
|
||||||
pattern = Chem.MolFromSmarts("[+1!h0!$([*]~[-1,-2,-3,-4]),-1!$([*]~[+1,+2,+3,+4])]")
|
|
||||||
at_matches = mol.GetSubstructMatches(pattern)
|
|
||||||
at_matches_list = [y[0] for y in at_matches]
|
|
||||||
if len(at_matches_list) > 0:
|
|
||||||
for at_idx in at_matches_list:
|
|
||||||
atom = mol.GetAtomWithIdx(at_idx)
|
|
||||||
chg = atom.GetFormalCharge()
|
|
||||||
hcount = atom.GetTotalNumHs()
|
|
||||||
atom.SetFormalCharge(0)
|
|
||||||
atom.SetNumExplicitHs(hcount - chg)
|
|
||||||
atom.UpdatePropertyCache()
|
|
||||||
return mol
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sanitize_smiles(smiles_list: List):
|
def sanitize_smiles(smiles_list: List):
|
||||||
parsed_smiles = []
|
parsed_smiles = []
|
||||||
@@ -353,28 +345,26 @@ class FormatConverter(object):
|
|||||||
# smi = smi.replace("@", "")
|
# smi = smi.replace("@", "")
|
||||||
|
|
||||||
mol = Chem.MolFromSmiles(smi)
|
mol = Chem.MolFromSmiles(smi)
|
||||||
mol = FormatConverter.neutralize_atoms(mol)
|
mol = FormatConverter.neutralize_molecule(mol)
|
||||||
|
Chem.RemoveStereochemistry(mol)
|
||||||
mol = Chem.RemoveAllHs(mol)
|
mol = Chem.RemoveAllHs(mol)
|
||||||
Chem.Kekulize(mol)
|
Chem.Kekulize(mol)
|
||||||
smi_p = Chem.MolToSmiles(mol, kekuleSmiles=True)
|
smi_p = Chem.MolToSmiles(mol, kekuleSmiles=True)
|
||||||
smi_p = Chem.CanonSmiles(smi_p)
|
smi_p = Chem.CanonSmiles(smi_p)
|
||||||
|
|
||||||
if '~' in smi_p:
|
if "~" in smi_p:
|
||||||
smi_p1 = smi_p.replace('~', '')
|
smi_p1 = smi_p.replace("~", "")
|
||||||
parsed_smiles.append(smi_p1)
|
parsed_smiles.append(smi_p1)
|
||||||
else:
|
else:
|
||||||
parsed_smiles.append(smi_p)
|
parsed_smiles.append(smi_p)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
errors += 1
|
errors += 1
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return parsed_smiles, errors
|
return parsed_smiles, errors
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Standardizer(ABC):
|
class Standardizer(ABC):
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
@@ -383,7 +373,6 @@ class Standardizer(ABC):
|
|||||||
|
|
||||||
|
|
||||||
class RuleStandardizer(Standardizer):
|
class RuleStandardizer(Standardizer):
|
||||||
|
|
||||||
def __init__(self, name, smirks):
|
def __init__(self, name, smirks):
|
||||||
super().__init__(name)
|
super().__init__(name)
|
||||||
self.smirks = smirks
|
self.smirks = smirks
|
||||||
@@ -392,8 +381,8 @@ class RuleStandardizer(Standardizer):
|
|||||||
standardized_smiles = list(set(FormatConverter.apply(smiles, self.smirks)))
|
standardized_smiles = list(set(FormatConverter.apply(smiles, self.smirks)))
|
||||||
|
|
||||||
if len(standardized_smiles) > 1:
|
if len(standardized_smiles) > 1:
|
||||||
logger.warning(f'{self.smirks} generated more than 1 compound {standardized_smiles}')
|
logger.warning(f"{self.smirks} generated more than 1 compound {standardized_smiles}")
|
||||||
print(f'{self.smirks} generated more than 1 compound {standardized_smiles}')
|
print(f"{self.smirks} generated more than 1 compound {standardized_smiles}")
|
||||||
standardized_smiles = standardized_smiles[:1]
|
standardized_smiles = standardized_smiles[:1]
|
||||||
|
|
||||||
if standardized_smiles:
|
if standardized_smiles:
|
||||||
@@ -403,7 +392,6 @@ class RuleStandardizer(Standardizer):
|
|||||||
|
|
||||||
|
|
||||||
class RegExStandardizer(Standardizer):
|
class RegExStandardizer(Standardizer):
|
||||||
|
|
||||||
def __init__(self, name, replacements: dict):
|
def __init__(self, name, replacements: dict):
|
||||||
super().__init__(name)
|
super().__init__(name)
|
||||||
self.replacements = replacements
|
self.replacements = replacements
|
||||||
@@ -423,28 +411,39 @@ class RegExStandardizer(Standardizer):
|
|||||||
return super().standardize(smi)
|
return super().standardize(smi)
|
||||||
|
|
||||||
|
|
||||||
FLATTEN = [
|
FLATTEN = [RegExStandardizer("Remove Stereo", {"@": ""})]
|
||||||
RegExStandardizer("Remove Stereo", {"@": ""})
|
|
||||||
]
|
|
||||||
|
|
||||||
UN_CIS_TRANS = [
|
UN_CIS_TRANS = [RegExStandardizer("Un-Cis-Trans", {"/": "", "\\": ""})]
|
||||||
RegExStandardizer("Un-Cis-Trans", {"/": "", "\\": ""})
|
|
||||||
]
|
|
||||||
|
|
||||||
BASIC = [
|
BASIC = [
|
||||||
RuleStandardizer("ammoniumstandardization", "[H][N+:1]([H])([H])[#6:2]>>[H][#7:1]([H])-[#6:2]"),
|
RuleStandardizer("ammoniumstandardization", "[H][N+:1]([H])([H])[#6:2]>>[H][#7:1]([H])-[#6:2]"),
|
||||||
RuleStandardizer("cyanate", "[H][#8:1][C:2]#[N:3]>>[#8-:1][C:2]#[N:3]"),
|
RuleStandardizer("cyanate", "[H][#8:1][C:2]#[N:3]>>[#8-:1][C:2]#[N:3]"),
|
||||||
RuleStandardizer("deprotonatecarboxyls", "[H][#8:1]-[#6:2]=[O:3]>>[#8-:1]-[#6:2]=[O:3]"),
|
RuleStandardizer("deprotonatecarboxyls", "[H][#8:1]-[#6:2]=[O:3]>>[#8-:1]-[#6:2]=[O:3]"),
|
||||||
RuleStandardizer("forNOOH", "[H][#8:1]-[#7+:2](-[*:3])=[O:4]>>[#8-:1]-[#7+:2](-[*:3])=[O:4]"),
|
RuleStandardizer("forNOOH", "[H][#8:1]-[#7+:2](-[*:3])=[O:4]>>[#8-:1]-[#7+:2](-[*:3])=[O:4]"),
|
||||||
RuleStandardizer("Hydroxylprotonation", "[#6;A:1][#6:2](-[#8-:3])=[#6;A:4]>>[#6:1]-[#6:2](-[#8:3][H])=[#6;A:4]"),
|
RuleStandardizer(
|
||||||
RuleStandardizer("phosphatedeprotonation", "[H][#8:1]-[$([#15]);!$(P([O-])):2]>>[#8-:1]-[#15:2]"),
|
"Hydroxylprotonation",
|
||||||
RuleStandardizer("PicricAcid",
|
"[#6;A:1][#6:2](-[#8-:3])=[#6;A:4]>>[#6:1]-[#6:2](-[#8:3][H])=[#6;A:4]",
|
||||||
"[H][#8:1]-[c:2]1[c:3][c:4][c:5]([c:6][c:7]1-[#7+:8](-[#8-:9])=[O:10])-[#7+:11](-[#8-:12])=[O:13]>>[#8-:1]-[c:2]1[c:3][c:4][c:5]([c:6][c:7]1-[#7+:8](-[#8-:9])=[O:10])-[#7+:11](-[#8-:12])=[O:13]"),
|
),
|
||||||
RuleStandardizer("Sulfate1", "[H][#8:1][S:2]([#8:3][H])(=[O:4])=[O:5]>>[#8-:1][S:2]([#8-:3])(=[O:4])=[O:5]"),
|
RuleStandardizer(
|
||||||
RuleStandardizer("Sulfate2",
|
"phosphatedeprotonation", "[H][#8:1]-[$([#15]);!$(P([O-])):2]>>[#8-:1]-[#15:2]"
|
||||||
"[#6:1]-[#8:2][S:3]([#8:4][H])(=[O:5])=[O:6]>>[#6:1]-[#8:2][S:3]([#8-:4])(=[O:5])=[O:6]"),
|
),
|
||||||
RuleStandardizer("Sulfate3", "[H][#8:3][S:2]([#6:1])(=[O:4])=[O:5]>>[#6:1][S:2]([#8-:3])(=[O:4])=[O:5]"),
|
RuleStandardizer(
|
||||||
RuleStandardizer("Transform_c1353forSOOH", "[H][#8:1][S:2]([*:3])=[O:4]>>[#8-:1][S:2]([*:3])=[O:4]"),
|
"PicricAcid",
|
||||||
|
"[H][#8:1]-[c:2]1[c:3][c:4][c:5]([c:6][c:7]1-[#7+:8](-[#8-:9])=[O:10])-[#7+:11](-[#8-:12])=[O:13]>>[#8-:1]-[c:2]1[c:3][c:4][c:5]([c:6][c:7]1-[#7+:8](-[#8-:9])=[O:10])-[#7+:11](-[#8-:12])=[O:13]",
|
||||||
|
),
|
||||||
|
RuleStandardizer(
|
||||||
|
"Sulfate1", "[H][#8:1][S:2]([#8:3][H])(=[O:4])=[O:5]>>[#8-:1][S:2]([#8-:3])(=[O:4])=[O:5]"
|
||||||
|
),
|
||||||
|
RuleStandardizer(
|
||||||
|
"Sulfate2",
|
||||||
|
"[#6:1]-[#8:2][S:3]([#8:4][H])(=[O:5])=[O:6]>>[#6:1]-[#8:2][S:3]([#8-:4])(=[O:5])=[O:6]",
|
||||||
|
),
|
||||||
|
RuleStandardizer(
|
||||||
|
"Sulfate3", "[H][#8:3][S:2]([#6:1])(=[O:4])=[O:5]>>[#6:1][S:2]([#8-:3])(=[O:4])=[O:5]"
|
||||||
|
),
|
||||||
|
RuleStandardizer(
|
||||||
|
"Transform_c1353forSOOH", "[H][#8:1][S:2]([*:3])=[O:4]>>[#8-:1][S:2]([*:3])=[O:4]"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
ENHANCED = BASIC + [
|
ENHANCED = BASIC + [
|
||||||
@@ -452,28 +451,30 @@ ENHANCED = BASIC + [
|
|||||||
]
|
]
|
||||||
|
|
||||||
EXOTIC = ENHANCED + [
|
EXOTIC = ENHANCED + [
|
||||||
RuleStandardizer("ThioPhosphate1", "[H][S:1]-[#15:2]=[$([#16]),$([#8]):3]>>[S-:1]-[#15:2]=[$([#16]),$([#8]):3]")
|
RuleStandardizer(
|
||||||
|
"ThioPhosphate1",
|
||||||
|
"[H][S:1]-[#15:2]=[$([#16]),$([#8]):3]>>[S-:1]-[#15:2]=[$([#16]),$([#8]):3]",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
COA_CUTTER = [
|
COA_CUTTER = [
|
||||||
RuleStandardizer("CutCoEnzymeAOff",
|
RuleStandardizer(
|
||||||
"CC(C)(COP(O)(=O)OP(O)(=O)OCC1OC(C(O)C1OP(O)(O)=O)n1cnc2c(N)ncnc12)C(O)C(=O)NCCC(=O)NCCS[$(*):1]>>[O-][$(*):1]")
|
"CutCoEnzymeAOff",
|
||||||
|
"CC(C)(COP(O)(=O)OP(O)(=O)OCC1OC(C(O)C1OP(O)(O)=O)n1cnc2c(N)ncnc12)C(O)C(=O)NCCC(=O)NCCS[$(*):1]>>[O-][$(*):1]",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
ENOL_KETO = [
|
ENOL_KETO = [RuleStandardizer("enol2Ketone", "[H][#8:2]-[#6:3]=[#6:1]>>[#6:1]-[#6:3]=[O:2]")]
|
||||||
RuleStandardizer("enol2Ketone", "[H][#8:2]-[#6:3]=[#6:1]>>[#6:1]-[#6:3]=[O:2]")
|
|
||||||
]
|
|
||||||
|
|
||||||
MATCH_STANDARDIZER = EXOTIC + FLATTEN + UN_CIS_TRANS + COA_CUTTER + ENOL_KETO
|
MATCH_STANDARDIZER = EXOTIC + FLATTEN + UN_CIS_TRANS + COA_CUTTER + ENOL_KETO
|
||||||
|
|
||||||
|
|
||||||
class IndigoUtils(object):
|
class IndigoUtils(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def layout(mol_data):
|
def layout(mol_data):
|
||||||
i = Indigo()
|
i = Indigo()
|
||||||
try:
|
try:
|
||||||
if mol_data.startswith('$RXN') or '>>' in mol_data:
|
if mol_data.startswith("$RXN") or ">>" in mol_data:
|
||||||
rxn = i.loadQueryReaction(mol_data)
|
rxn = i.loadQueryReaction(mol_data)
|
||||||
rxn.layout()
|
rxn.layout()
|
||||||
return rxn.rxnfile()
|
return rxn.rxnfile()
|
||||||
@@ -481,14 +482,14 @@ class IndigoUtils(object):
|
|||||||
mol = i.loadQueryMolecule(mol_data)
|
mol = i.loadQueryMolecule(mol_data)
|
||||||
mol.layout()
|
mol.layout()
|
||||||
return mol.molfile()
|
return mol.molfile()
|
||||||
except IndigoException as e:
|
except IndigoException:
|
||||||
try:
|
try:
|
||||||
logger.info("layout() failed, trying loadReactionSMARTS as fallback!")
|
logger.info("layout() failed, trying loadReactionSMARTS as fallback!")
|
||||||
rxn = IndigoUtils.load_reaction_SMARTS(mol_data)
|
rxn = IndigoUtils.load_reaction_SMARTS(mol_data)
|
||||||
rxn.layout()
|
rxn.layout()
|
||||||
return rxn.molfile()
|
return rxn.molfile()
|
||||||
except IndigoException as e2:
|
except IndigoException as e2:
|
||||||
logger.error(f'layout() failed due to {e2}!')
|
logger.error(f"layout() failed due to {e2}!")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_reaction_SMARTS(mol):
|
def load_reaction_SMARTS(mol):
|
||||||
@@ -498,7 +499,7 @@ class IndigoUtils(object):
|
|||||||
def aromatize(mol_data, is_query):
|
def aromatize(mol_data, is_query):
|
||||||
i = Indigo()
|
i = Indigo()
|
||||||
try:
|
try:
|
||||||
if mol_data.startswith('$RXN'):
|
if mol_data.startswith("$RXN"):
|
||||||
if is_query:
|
if is_query:
|
||||||
rxn = i.loadQueryReaction(mol_data)
|
rxn = i.loadQueryReaction(mol_data)
|
||||||
else:
|
else:
|
||||||
@@ -514,20 +515,20 @@ class IndigoUtils(object):
|
|||||||
|
|
||||||
mol.aromatize()
|
mol.aromatize()
|
||||||
return mol.molfile()
|
return mol.molfile()
|
||||||
except IndigoException as e:
|
except IndigoException:
|
||||||
try:
|
try:
|
||||||
logger.info("Aromatizing failed, trying loadReactionSMARTS as fallback!")
|
logger.info("Aromatizing failed, trying loadReactionSMARTS as fallback!")
|
||||||
rxn = IndigoUtils.load_reaction_SMARTS(mol_data)
|
rxn = IndigoUtils.load_reaction_SMARTS(mol_data)
|
||||||
rxn.aromatize()
|
rxn.aromatize()
|
||||||
return rxn.molfile()
|
return rxn.molfile()
|
||||||
except IndigoException as e2:
|
except IndigoException as e2:
|
||||||
logger.error(f'Aromatizing failed due to {e2}!')
|
logger.error(f"Aromatizing failed due to {e2}!")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def dearomatize(mol_data, is_query):
|
def dearomatize(mol_data, is_query):
|
||||||
i = Indigo()
|
i = Indigo()
|
||||||
try:
|
try:
|
||||||
if mol_data.startswith('$RXN'):
|
if mol_data.startswith("$RXN"):
|
||||||
if is_query:
|
if is_query:
|
||||||
rxn = i.loadQueryReaction(mol_data)
|
rxn = i.loadQueryReaction(mol_data)
|
||||||
else:
|
else:
|
||||||
@@ -543,14 +544,14 @@ class IndigoUtils(object):
|
|||||||
|
|
||||||
mol.dearomatize()
|
mol.dearomatize()
|
||||||
return mol.molfile()
|
return mol.molfile()
|
||||||
except IndigoException as e:
|
except IndigoException:
|
||||||
try:
|
try:
|
||||||
logger.info("De-Aromatizing failed, trying loadReactionSMARTS as fallback!")
|
logger.info("De-Aromatizing failed, trying loadReactionSMARTS as fallback!")
|
||||||
rxn = IndigoUtils.load_reaction_SMARTS(mol_data)
|
rxn = IndigoUtils.load_reaction_SMARTS(mol_data)
|
||||||
rxn.dearomatize()
|
rxn.dearomatize()
|
||||||
return rxn.molfile()
|
return rxn.molfile()
|
||||||
except IndigoException as e2:
|
except IndigoException as e2:
|
||||||
logger.error(f'De-Aromatizing failed due to {e2}!')
|
logger.error(f"De-Aromatizing failed due to {e2}!")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sanitize_functional_group(functional_group: str):
|
def sanitize_functional_group(functional_group: str):
|
||||||
@@ -562,7 +563,7 @@ class IndigoUtils(object):
|
|||||||
|
|
||||||
# special environment handling (amines, hydroxy, esters, ethers)
|
# special environment handling (amines, hydroxy, esters, ethers)
|
||||||
# the higher substituted should not contain H env.
|
# the higher substituted should not contain H env.
|
||||||
if functional_group == '[C]=O':
|
if functional_group == "[C]=O":
|
||||||
functional_group = "[H][C](=O)[CX4,c]"
|
functional_group = "[H][C](=O)[CX4,c]"
|
||||||
|
|
||||||
# aldamines
|
# aldamines
|
||||||
@@ -596,15 +597,20 @@ class IndigoUtils(object):
|
|||||||
functional_group = "[nH1,nX2](a)a" # pyrrole (with H) or pyridine (no other connections); currently overlaps with neighboring aromatic atoms
|
functional_group = "[nH1,nX2](a)a" # pyrrole (with H) or pyridine (no other connections); currently overlaps with neighboring aromatic atoms
|
||||||
|
|
||||||
# substituted aromatic nitrogen
|
# substituted aromatic nitrogen
|
||||||
functional_group = functional_group.replace("N*(R)R",
|
functional_group = functional_group.replace(
|
||||||
"n(a)a") # substituent will be before N*; currently overlaps with neighboring aromatic atoms
|
"N*(R)R", "n(a)a"
|
||||||
|
) # substituent will be before N*; currently overlaps with neighboring aromatic atoms
|
||||||
# pyridinium
|
# pyridinium
|
||||||
if functional_group == "RN*(R)(R)(R)R":
|
if functional_group == "RN*(R)(R)(R)R":
|
||||||
functional_group = "[CX4,c]n(a)a" # currently overlaps with neighboring aromatic atoms
|
functional_group = (
|
||||||
|
"[CX4,c]n(a)a" # currently overlaps with neighboring aromatic atoms
|
||||||
|
)
|
||||||
|
|
||||||
# N-oxide
|
# N-oxide
|
||||||
if functional_group == "[H]ON*(R)(R)(R)R":
|
if functional_group == "[H]ON*(R)(R)(R)R":
|
||||||
functional_group = "[O-][n+](a)a" # currently overlaps with neighboring aromatic atoms
|
functional_group = (
|
||||||
|
"[O-][n+](a)a" # currently overlaps with neighboring aromatic atoms
|
||||||
|
)
|
||||||
|
|
||||||
# other aromatic hetero atoms
|
# other aromatic hetero atoms
|
||||||
functional_group = functional_group.replace("C*", "c")
|
functional_group = functional_group.replace("C*", "c")
|
||||||
@@ -617,7 +623,9 @@ class IndigoUtils(object):
|
|||||||
# other replacement, to accomodate for the standardization rules in enviPath
|
# other replacement, to accomodate for the standardization rules in enviPath
|
||||||
# This is not the perfect way to do it; there should be a way to replace substructure SMARTS in SMARTS?
|
# This is not the perfect way to do it; there should be a way to replace substructure SMARTS in SMARTS?
|
||||||
# nitro groups are broken, due to charge handling. this SMARTS matches both forms (formal charges and hypervalent); Ertl-CDK still treats both forms separately...
|
# nitro groups are broken, due to charge handling. this SMARTS matches both forms (formal charges and hypervalent); Ertl-CDK still treats both forms separately...
|
||||||
functional_group = functional_group.replace("[H]O[N](=O)R", "[CX4,c][NX3](~[OX1])~[OX1]")
|
functional_group = functional_group.replace(
|
||||||
|
"[H]O[N](=O)R", "[CX4,c][NX3](~[OX1])~[OX1]"
|
||||||
|
)
|
||||||
functional_group = functional_group.replace("O=N(=O)R", "[CX4,c][NX3](~[OX1])~[OX1]")
|
functional_group = functional_group.replace("O=N(=O)R", "[CX4,c][NX3](~[OX1])~[OX1]")
|
||||||
# carboxylic acid: this SMARTS matches both neutral and anionic form; includes COOH in larger functional_groups
|
# carboxylic acid: this SMARTS matches both neutral and anionic form; includes COOH in larger functional_groups
|
||||||
functional_group = functional_group.replace("[H]OC(=O)", "[OD1]C(=O)")
|
functional_group = functional_group.replace("[H]OC(=O)", "[OD1]C(=O)")
|
||||||
@@ -635,7 +643,9 @@ class IndigoUtils(object):
|
|||||||
return functional_group
|
return functional_group
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _colorize(indigo: Indigo, molecule: IndigoObject, functional_groups: Dict[str, int], is_reaction: bool):
|
def _colorize(
|
||||||
|
indigo: Indigo, molecule: IndigoObject, functional_groups: Dict[str, int], is_reaction: bool
|
||||||
|
):
|
||||||
indigo.setOption("render-atom-color-property", "color")
|
indigo.setOption("render-atom-color-property", "color")
|
||||||
indigo.setOption("aromaticity-model", "generic")
|
indigo.setOption("aromaticity-model", "generic")
|
||||||
|
|
||||||
@@ -665,7 +675,6 @@ class IndigoUtils(object):
|
|||||||
|
|
||||||
for match in matcher.iterateMatches(query):
|
for match in matcher.iterateMatches(query):
|
||||||
if match is not None:
|
if match is not None:
|
||||||
|
|
||||||
for atom in query.iterateAtoms():
|
for atom in query.iterateAtoms():
|
||||||
mappedAtom = match.mapAtom(atom)
|
mappedAtom = match.mapAtom(atom)
|
||||||
if mappedAtom is None or mappedAtom.index() in environment:
|
if mappedAtom is None or mappedAtom.index() in environment:
|
||||||
@@ -674,7 +683,7 @@ class IndigoUtils(object):
|
|||||||
counts[mappedAtom.index()] = max(v, counts[mappedAtom.index()])
|
counts[mappedAtom.index()] = max(v, counts[mappedAtom.index()])
|
||||||
|
|
||||||
except IndigoException as e:
|
except IndigoException as e:
|
||||||
logger.debug(f'Colorizing failed due to {e}')
|
logger.debug(f"Colorizing failed due to {e}")
|
||||||
|
|
||||||
for k, v in counts.items():
|
for k, v in counts.items():
|
||||||
if is_reaction:
|
if is_reaction:
|
||||||
@@ -688,8 +697,9 @@ class IndigoUtils(object):
|
|||||||
molecule.addDataSGroup([k], [], "color", color)
|
molecule.addDataSGroup([k], [], "color", color)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mol_to_svg(mol_data: str, width: int = 0, height: int = 0, functional_groups: Dict[str, int] = None):
|
def mol_to_svg(
|
||||||
|
mol_data: str, width: int = 0, height: int = 0, functional_groups: Dict[str, int] = None
|
||||||
|
):
|
||||||
if functional_groups is None:
|
if functional_groups is None:
|
||||||
functional_groups = {}
|
functional_groups = {}
|
||||||
|
|
||||||
@@ -701,7 +711,7 @@ class IndigoUtils(object):
|
|||||||
i.setOption("render-image-size", width, height)
|
i.setOption("render-image-size", width, height)
|
||||||
i.setOption("render-bond-line-width", 2.0)
|
i.setOption("render-bond-line-width", 2.0)
|
||||||
|
|
||||||
if '~' in mol_data:
|
if "~" in mol_data:
|
||||||
mol = i.loadSmarts(mol_data)
|
mol = i.loadSmarts(mol_data)
|
||||||
else:
|
else:
|
||||||
mol = i.loadMolecule(mol_data)
|
mol = i.loadMolecule(mol_data)
|
||||||
@@ -709,11 +719,18 @@ class IndigoUtils(object):
|
|||||||
if len(functional_groups.keys()) > 0:
|
if len(functional_groups.keys()) > 0:
|
||||||
IndigoUtils._colorize(i, mol, functional_groups, False)
|
IndigoUtils._colorize(i, mol, functional_groups, False)
|
||||||
|
|
||||||
return renderer.renderToBuffer(mol).decode('UTF-8')
|
return renderer.renderToBuffer(mol).decode("UTF-8")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def smirks_to_svg(smirks: str, is_query_smirks, width: int = 0, height: int = 0,
|
def smirks_to_svg(
|
||||||
educt_functional_groups: Dict[str, int] = None, product_functional_groups: Dict[str, int] = None):
|
smirks: str,
|
||||||
|
is_query_smirks,
|
||||||
|
width: int = 0,
|
||||||
|
height: int = 0,
|
||||||
|
educt_functional_groups: Dict[str, int] = None,
|
||||||
|
product_functional_groups: Dict[str, int] = None,
|
||||||
|
debug: bool = False,
|
||||||
|
):
|
||||||
if educt_functional_groups is None:
|
if educt_functional_groups is None:
|
||||||
educt_functional_groups = {}
|
educt_functional_groups = {}
|
||||||
|
|
||||||
@@ -723,6 +740,11 @@ class IndigoUtils(object):
|
|||||||
i = Indigo()
|
i = Indigo()
|
||||||
renderer = IndigoRenderer(i)
|
renderer = IndigoRenderer(i)
|
||||||
|
|
||||||
|
if debug:
|
||||||
|
i.setOption("render-atom-ids-visible", True)
|
||||||
|
i.setOption("render-bond-ids-visible", False)
|
||||||
|
i.setOption("render-atom-bond-ids-from-one", True)
|
||||||
|
|
||||||
i.setOption("render-output-format", "svg")
|
i.setOption("render-output-format", "svg")
|
||||||
i.setOption("render-coloring", True)
|
i.setOption("render-coloring", True)
|
||||||
i.setOption("render-image-size", width, height)
|
i.setOption("render-image-size", width, height)
|
||||||
@@ -740,18 +762,18 @@ class IndigoUtils(object):
|
|||||||
for prod in obj.iterateProducts():
|
for prod in obj.iterateProducts():
|
||||||
IndigoUtils._colorize(i, prod, product_functional_groups, True)
|
IndigoUtils._colorize(i, prod, product_functional_groups, True)
|
||||||
|
|
||||||
return renderer.renderToBuffer(obj).decode('UTF-8')
|
return renderer.renderToBuffer(obj).decode("UTF-8")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
data = {
|
data = {
|
||||||
"struct": "\n Ketcher 2172510 12D 1 1.00000 0.00000 0\n\n 6 6 0 0 0 999 V2000\n 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -1.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -1.5000 -0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -1.0000 -1.7321 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.0000 -1.7321 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.5000 -0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 1 2 2 0 0 0 0\n 2 3 1 0 0 0 0\n 3 4 2 0 0 0 0\n 4 5 1 0 0 0 0\n 5 6 2 0 0 0 0\n 6 1 1 0 0 0 0\nM END\n",
|
"struct": "\n Ketcher 2172510 12D 1 1.00000 0.00000 0\n\n 6 6 0 0 0 999 V2000\n 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -1.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -1.5000 -0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n -1.0000 -1.7321 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.0000 -1.7321 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.5000 -0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 1 2 2 0 0 0 0\n 2 3 1 0 0 0 0\n 3 4 2 0 0 0 0\n 4 5 1 0 0 0 0\n 5 6 2 0 0 0 0\n 6 1 1 0 0 0 0\nM END\n",
|
||||||
"options": {
|
"options": {
|
||||||
"smart-layout": True,
|
"smart-layout": True,
|
||||||
"ignore-stereochemistry-errors": True,
|
"ignore-stereochemistry-errors": True,
|
||||||
"mass-skip-error-on-pseudoatoms": False,
|
"mass-skip-error-on-pseudoatoms": False,
|
||||||
"gross-formula-add-rsites": True
|
"gross-formula-add-rsites": True,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
print(IndigoUtils.aromatize(data['struct'], False))
|
print(IndigoUtils.aromatize(data["struct"], False))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user