forked from enviPath/enviPy
Compare commits
17 Commits
beta_2025-
...
beta_2025-
| Author | SHA1 | Date | |
|---|---|---|---|
| d5ebb23622 | |||
| 93dd811e39 | |||
| 9a4735246f | |||
| 1f863fdcd6 | |||
| 1effaeb342 | |||
| 386098b8a6 | |||
| ef697ac5f5 | |||
| 68a3f3b982 | |||
| afeb56622c | |||
| 22f0bbe10b | |||
| 36879c266b | |||
| c2c46fbfa7 | |||
| d2f4fdc58a | |||
| 3f2b046bd6 | |||
| 7ad4112343 | |||
| 3f5bb76633 | |||
| b757a07f91 |
22
.env.local.example
Normal file
22
.env.local.example
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Django settings
|
||||||
|
SECRET_KEY='a-secure-secret-key-for-development'
|
||||||
|
DEBUG=True
|
||||||
|
ALLOWED_HOSTS=*
|
||||||
|
|
||||||
|
# Database settings (using PostgreSQL for local development)
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=postgres
|
||||||
|
POSTGRES_DB=envipath
|
||||||
|
POSTGRES_SERVICE_NAME=localhost
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
|
||||||
|
# Celery settings
|
||||||
|
CELERY_BROKER_URL='redis://localhost:6379/0'
|
||||||
|
CELERY_RESULT_BACKEND='redis://localhost:6379/0'
|
||||||
|
FLAG_CELERY_PRESENT=False
|
||||||
|
|
||||||
|
# Other settings
|
||||||
|
LOG_LEVEL='INFO'
|
||||||
|
SERVER_URL='http://localhost:8000'
|
||||||
|
PLUGINS_ENABLED=True
|
||||||
|
EP_DATA_DIR='data'
|
||||||
@ -16,4 +16,3 @@ POSTGRES_PORT=
|
|||||||
# MAIL
|
# MAIL
|
||||||
EMAIL_HOST_USER=
|
EMAIL_HOST_USER=
|
||||||
EMAIL_HOST_PASSWORD=
|
EMAIL_HOST_PASSWORD=
|
||||||
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -5,4 +5,8 @@ static/admin/
|
|||||||
static/django_extensions/
|
static/django_extensions/
|
||||||
.env
|
.env
|
||||||
debug.log
|
debug.log
|
||||||
scratches/
|
scratches/
|
||||||
|
|
||||||
|
data/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|||||||
30
.pre-commit-config.yaml
Normal file
30
.pre-commit-config.yaml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v3.2.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.13.3
|
||||||
|
hooks:
|
||||||
|
# Run the linter.
|
||||||
|
- id: ruff-check
|
||||||
|
types_or: [python, pyi]
|
||||||
|
args: [--fix]
|
||||||
|
# Run the formatter.
|
||||||
|
- id: ruff-format
|
||||||
|
types_or: [python, pyi]
|
||||||
|
|
||||||
|
# - repo: local
|
||||||
|
# hooks:
|
||||||
|
# - id: django-check
|
||||||
|
# name: Run Django Check
|
||||||
|
# entry: uv run python manage.py check
|
||||||
|
# language: system
|
||||||
|
# pass_filenames: false
|
||||||
|
# types: [python]
|
||||||
@ -1 +1 @@
|
|||||||
3.10
|
3.12
|
||||||
|
|||||||
89
README.md
89
README.md
@ -1,2 +1,91 @@
|
|||||||
# enviPy
|
# enviPy
|
||||||
|
|
||||||
|
## Local Development Setup
|
||||||
|
|
||||||
|
These instructions will guide you through setting up the project for local development.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Python 3.11 or later
|
||||||
|
- [uv](https://github.com/astral-sh/uv) - A fast Python package installer and resolver.
|
||||||
|
- **Docker and Docker Compose** - Required for running the PostgreSQL database.
|
||||||
|
- Git
|
||||||
|
|
||||||
|
> **Note:** This application requires PostgreSQL, which uses `ArrayField`. Docker is the recommended way to run PostgreSQL locally.
|
||||||
|
|
||||||
|
### 1. Install Dependencies
|
||||||
|
|
||||||
|
This project uses `uv` to manage dependencies and `poe-the-poet` for task running. First, [install `uv` if you don't have it yet](https://docs.astral.sh/uv/guides/install-python/).
|
||||||
|
|
||||||
|
Then, sync the project dependencies. This will create a virtual environment in `.venv` and install all necessary packages, including `poe-the-poet`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync --dev
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note on RDkit:** If you have a different version of rdkit installed globally, the dependency installation may fail. If this happens, please uninstall the global version and run `uv sync` again.
|
||||||
|
|
||||||
|
### 2. Set Up Environment File
|
||||||
|
|
||||||
|
Copy the example environment file for local setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.local.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
This file contains the necessary environment variables for local development.
|
||||||
|
|
||||||
|
### 3. Quick Setup with Poe
|
||||||
|
|
||||||
|
The easiest way to set up the development environment is by using the `poe` task runner, which is executed via `uv run`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run poe setup
|
||||||
|
```
|
||||||
|
|
||||||
|
This single command will:
|
||||||
|
1. Start the PostgreSQL database using Docker Compose.
|
||||||
|
2. Run database migrations.
|
||||||
|
3. Bootstrap initial data (anonymous user, default packages, models).
|
||||||
|
|
||||||
|
After setup, start the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run poe dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The application will be available at `http://localhost:8000`.
|
||||||
|
|
||||||
|
#### Other useful Poe commands:
|
||||||
|
|
||||||
|
You can list all available commands by running `uv run poe --help`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run poe db-up # Start PostgreSQL only
|
||||||
|
uv run poe db-down # Stop PostgreSQL
|
||||||
|
uv run poe migrate # Run migrations only
|
||||||
|
uv run poe bootstrap # Bootstrap data only
|
||||||
|
uv run poe shell # Open the Django shell
|
||||||
|
uv run poe clean # Remove database volumes (WARNING: destroys all data)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
* **Docker Connection Error:** If you see an error like `open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified` (on Windows), it likely means your Docker Desktop application is not running. Please start Docker Desktop and try the command again.
|
||||||
|
|
||||||
|
* **SSH Keys for Git Dependencies:** Some dependencies are installed from private git repositories and require SSH authentication. Ensure your SSH keys are configured correctly for Git.
|
||||||
|
* For a general guide, see [GitHub's official documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
|
||||||
|
* **Windows Users:** If `uv sync` hangs while fetching git dependencies, you may need to explicitly configure Git to use the Windows OpenSSH client and use the `ssh-agent` to manage your key's passphrase.
|
||||||
|
|
||||||
|
1. **Point Git to the correct SSH executable:**
|
||||||
|
```powershell
|
||||||
|
git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"
|
||||||
|
```
|
||||||
|
2. **Enable and use the SSH agent:**
|
||||||
|
```powershell
|
||||||
|
# Run these commands in an administrator PowerShell
|
||||||
|
Get-Service ssh-agent | Set-Service -StartupType Automatic -PassThru | Start-Service
|
||||||
|
|
||||||
|
# Add your key to the agent. It will prompt for the passphrase once.
|
||||||
|
ssh-add
|
||||||
|
```
|
||||||
|
|||||||
20
docker-compose.dev.yml
Normal file
20
docker-compose.dev.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
container_name: envipath-postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: envipath
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
@ -2,4 +2,4 @@
|
|||||||
# Django starts so that shared_task will use this app.
|
# 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")
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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(",")
|
||||||
|
|||||||
@ -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")))
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
13
epdb/api.py
13
epdb/api.py
@ -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,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()
|
|
||||||
File diff suppressed because it is too large
Load Diff
1047
epdb/logic.py
1047
epdb/logic.py
File diff suppressed because it is too large
Load Diff
@ -5,32 +5,49 @@ from django.core.management.base import BaseCommand
|
|||||||
from django.db import transaction
|
from 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)
|
||||||
|
|||||||
105
epdb/management/commands/create_ml_models.py
Normal file
105
epdb/management/commands/create_ml_models.py
Normal file
@ -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()
|
||||||
58
epdb/management/commands/import_external_identifiers.py
Normal file
58
epdb/management/commands/import_external_identifiers.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from csv import DictReader
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.models import Compound, CompoundStructure, Reaction, ExternalDatabase, ExternalIdentifier
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
STR_TO_MODEL = {
|
||||||
|
"Compound": Compound,
|
||||||
|
"CompoundStructure": CompoundStructure,
|
||||||
|
"Reaction": Reaction,
|
||||||
|
}
|
||||||
|
|
||||||
|
STR_TO_DATABASE = {
|
||||||
|
"ChEBI": ExternalDatabase.objects.get(name="ChEBI"),
|
||||||
|
"RHEA": ExternalDatabase.objects.get(name="RHEA"),
|
||||||
|
"KEGG Reaction": ExternalDatabase.objects.get(name="KEGG Reaction"),
|
||||||
|
"PubChem Compound": ExternalDatabase.objects.get(name="PubChem Compound"),
|
||||||
|
"PubChem Substance": ExternalDatabase.objects.get(name="PubChem Substance"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--data",
|
||||||
|
type=str,
|
||||||
|
help="Path of the ID Mapping file.",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--replace-host",
|
||||||
|
type=str,
|
||||||
|
help="Replace https://envipath.org/ with this host, e.g. http://localhost:8000/",
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
with open(options["data"]) as fh:
|
||||||
|
reader = DictReader(fh)
|
||||||
|
for row in reader:
|
||||||
|
clz = self.STR_TO_MODEL[row["model"]]
|
||||||
|
|
||||||
|
url = row["url"]
|
||||||
|
if options["replace_host"]:
|
||||||
|
url = url.replace("https://envipath.org/", options["replace_host"])
|
||||||
|
|
||||||
|
instance = clz.objects.get(url=url)
|
||||||
|
db = self.STR_TO_DATABASE[row["identifier_type"]]
|
||||||
|
|
||||||
|
ExternalIdentifier.objects.create(
|
||||||
|
content_object=instance,
|
||||||
|
database=db,
|
||||||
|
identifier_value=row["identifier_value"],
|
||||||
|
url=db.url_pattern.format(id=row["identifier_value"]),
|
||||||
|
is_primary=False,
|
||||||
|
)
|
||||||
@ -1,27 +1,29 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
64
epdb/migrations/0008_enzymelink.py
Normal file
64
epdb/migrations/0008_enzymelink.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-10 06:58
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0007_alter_enviformer_options_enviformer_app_domain_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="EnzymeLink",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
model_utils.fields.AutoCreatedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"uuid",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4, unique=True, verbose_name="UUID of this object"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.TextField(default="no name", verbose_name="Name")),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.TextField(default="no description", verbose_name="Descriptions"),
|
||||||
|
),
|
||||||
|
("url", models.TextField(null=True, unique=True, verbose_name="URL")),
|
||||||
|
("kv", models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
("ec_number", models.TextField(verbose_name="EC Number")),
|
||||||
|
("classification_level", models.IntegerField(verbose_name="Classification Level")),
|
||||||
|
("linking_method", models.TextField(verbose_name="Linking Method")),
|
||||||
|
("edge_evidence", models.ManyToManyField(to="epdb.edge")),
|
||||||
|
("reaction_evidence", models.ManyToManyField(to="epdb.reaction")),
|
||||||
|
(
|
||||||
|
"rule",
|
||||||
|
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="epdb.rule"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
2195
epdb/models.py
2195
epdb/models.py
File diff suppressed because it is too large
Load Diff
@ -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.
|
|
||||||
246
epdb/urls.py
246
epdb/urls.py
@ -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"),
|
||||||
]
|
]
|
||||||
|
|||||||
2395
epdb/views.py
2395
epdb/views.py
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
BIN
fixtures/models/2750eaca-bc13-4018-81c2-f7f9d94bc435_ds.pkl
Normal file
BIN
fixtures/models/2750eaca-bc13-4018-81c2-f7f9d94bc435_ds.pkl
Normal file
Binary file not shown.
BIN
fixtures/models/2750eaca-bc13-4018-81c2-f7f9d94bc435_mod.pkl
Normal file
BIN
fixtures/models/2750eaca-bc13-4018-81c2-f7f9d94bc435_mod.pkl
Normal file
Binary file not shown.
8328
fixtures/packages/2025-07-18/external_identifiers.csv
Normal file
8328
fixtures/packages/2025-07-18/external_identifiers.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
fixtures/test_fixtures_incl_model.jsonl.gz
Normal file
BIN
fixtures/test_fixtures_incl_model.jsonl.gz
Normal file
Binary file not shown.
@ -12,4 +12,5 @@ urlpatterns = [
|
|||||||
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/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'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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"])
|
||||||
|
|||||||
@ -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" }
|
||||||
|
|||||||
BIN
static/images/UoA-Logo-Primary-RGB-Large.png
Normal file
BIN
static/images/UoA-Logo-Primary-RGB-Large.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
BIN
static/images/UoA-Logo-Primary-RGB-Reversed-Large.png
Normal file
BIN
static/images/UoA-Logo-Primary-RGB-Reversed-Large.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
BIN
static/images/UoA-Logo-Primary-RGB-Reversed-Small.png
Normal file
BIN
static/images/UoA-Logo-Primary-RGB-Reversed-Small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
static/images/UoA-Logo-Primary-RGB-Small.png
Normal file
BIN
static/images/UoA-Logo-Primary-RGB-Small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
488
static/images/uzh-logo.svg
Normal file
488
static/images/uzh-logo.svg
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
|
||||||
|
<svg version="1.1" baseProfile="tiny" id="Universität_Zürich"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="-0.499 -0.501 142.73 49.19" xml:space="preserve">
|
||||||
|
<path d="M6.818,32.721C6.296,32.944,6.2,32.986,6.093,33.04c-0.113,0.058-0.156,0.108-0.133,0.231
|
||||||
|
c0.005,0.029,0.026,0.091,0.047,0.139c0.017,0.038,0.019,0.062-0.006,0.071c-0.023,0.011-0.041-0.01-0.062-0.062
|
||||||
|
c-0.078-0.182-0.161-0.401-0.205-0.506c-0.035-0.082-0.138-0.295-0.191-0.418c-0.021-0.052-0.024-0.079-0.001-0.089
|
||||||
|
c0.024-0.011,0.04,0.007,0.056,0.042c0.016,0.037,0.027,0.057,0.053,0.095c0.057,0.086,0.127,0.088,0.25,0.043
|
||||||
|
c0.113-0.04,0.209-0.081,0.731-0.305l0.481-0.206c0.499-0.215,0.666-0.404,0.729-0.631c0.062-0.21,0.008-0.373-0.043-0.49
|
||||||
|
c-0.063-0.151-0.191-0.315-0.389-0.395c-0.27-0.108-0.584,0.014-0.938,0.166l-0.431,0.184c-0.521,0.225-0.619,0.266-0.726,0.319
|
||||||
|
c-0.114,0.058-0.157,0.107-0.132,0.23c0.005,0.031,0.025,0.091,0.042,0.129c0.016,0.037,0.018,0.062-0.006,0.071
|
||||||
|
c-0.024,0.011-0.041-0.011-0.062-0.06C5.083,31.425,5.001,31.204,5,31.201c-0.018-0.042-0.121-0.254-0.178-0.387
|
||||||
|
c-0.021-0.048-0.024-0.075,0-0.085c0.024-0.012,0.04,0.007,0.057,0.048c0.017,0.038,0.029,0.058,0.054,0.096
|
||||||
|
c0.057,0.085,0.127,0.088,0.25,0.043c0.112-0.04,0.209-0.082,0.73-0.306l0.368-0.157c0.382-0.164,0.803-0.3,1.175-0.117
|
||||||
|
c0.314,0.154,0.458,0.387,0.554,0.608c0.078,0.183,0.21,0.517,0.091,0.865c-0.083,0.242-0.281,0.481-0.78,0.695L6.818,32.721z
|
||||||
|
M6.676,28.899c0.283-0.069,0.368-0.139,0.378-0.217c0.008-0.066-0.004-0.138-0.017-0.195c-0.009-0.04-0.007-0.063,0.017-0.068
|
||||||
|
c0.028-0.006,0.043,0.025,0.055,0.076c0.052,0.237,0.073,0.386,0.088,0.455c0.007,0.033,0.056,0.202,0.098,0.393
|
||||||
|
c0.011,0.048,0.015,0.081-0.019,0.089c-0.022,0.005-0.034-0.017-0.042-0.053c-0.01-0.048-0.028-0.111-0.054-0.16
|
||||||
|
c-0.051-0.089-0.159-0.079-0.479-0.015l-2.169,0.442c-0.073,0.016-0.127,0.016-0.135-0.021c-0.009-0.04,0.031-0.083,0.083-0.16
|
||||||
|
c0.038-0.054,0.511-0.739,0.924-1.39c0.194-0.303,0.612-0.911,0.657-0.986l-0.004-0.018l-1.633,0.396
|
||||||
|
c-0.222,0.053-0.283,0.102-0.301,0.196c-0.01,0.061,0.009,0.146,0.021,0.198c0.01,0.044,0.002,0.061-0.021,0.064
|
||||||
|
c-0.028,0.008-0.042-0.032-0.054-0.088C4.028,27.651,4,27.474,3.984,27.397c-0.009-0.041-0.05-0.178-0.09-0.356
|
||||||
|
c-0.011-0.048-0.016-0.085,0.015-0.091c0.022-0.005,0.038,0.015,0.047,0.059c0.008,0.036,0.015,0.065,0.035,0.106
|
||||||
|
c0.051,0.097,0.135,0.108,0.34,0.067l2.313-0.467c0.08-0.018,0.115-0.01,0.124,0.022c0.009,0.041-0.021,0.094-0.054,0.143
|
||||||
|
c-0.168,0.277-0.544,0.852-0.837,1.312c-0.308,0.484-0.675,0.99-0.729,1.071l0.002,0.011L6.676,28.899z M4.871,25.556
|
||||||
|
c-0.566,0.035-0.672,0.042-0.791,0.058c-0.126,0.016-0.188,0.057-0.201,0.14C3.87,25.796,3.87,25.844,3.873,25.89
|
||||||
|
c0.002,0.036-0.004,0.06-0.034,0.062c-0.021,0.002-0.031-0.027-0.035-0.087c-0.01-0.142-0.014-0.378-0.021-0.486
|
||||||
|
c-0.006-0.093-0.031-0.312-0.04-0.455c-0.003-0.048,0.003-0.079,0.025-0.081c0.029-0.002,0.039,0.021,0.041,0.059
|
||||||
|
c0.002,0.037,0.008,0.066,0.018,0.111c0.025,0.1,0.09,0.126,0.222,0.123c0.12,0,0.225-0.006,0.792-0.042l0.657-0.042
|
||||||
|
c0.362-0.021,0.657-0.041,0.816-0.065c0.1-0.019,0.166-0.048,0.173-0.161c0.004-0.053,0.006-0.135,0.003-0.191
|
||||||
|
c-0.002-0.042,0.007-0.061,0.026-0.062c0.025-0.001,0.039,0.028,0.042,0.069c0.016,0.246,0.02,0.48,0.025,0.583
|
||||||
|
c0.005,0.086,0.031,0.319,0.041,0.47c0.003,0.048-0.006,0.075-0.033,0.076c-0.019,0.002-0.03-0.014-0.033-0.059
|
||||||
|
c-0.003-0.056-0.015-0.1-0.023-0.133c-0.02-0.074-0.084-0.093-0.193-0.097c-0.157-0.009-0.452,0.01-0.814,0.032L4.871,25.556z
|
||||||
|
M4.071,23.877c-0.21,0.073-0.256,0.134-0.302,0.267c-0.02,0.055-0.022,0.114-0.024,0.145c-0.002,0.034-0.014,0.044-0.037,0.043
|
||||||
|
c-0.029-0.002-0.031-0.043-0.026-0.099c0.013-0.198,0.038-0.41,0.045-0.541c0.006-0.093,0.006-0.273,0.019-0.46
|
||||||
|
c0.003-0.045,0.013-0.085,0.039-0.084c0.026,0.002,0.032,0.024,0.03,0.059c-0.003,0.061-0.003,0.116,0.017,0.147
|
||||||
|
c0.018,0.026,0.042,0.041,0.076,0.042c0.049,0.003,0.155-0.024,0.293-0.067l1.736-0.534l0.001-0.015
|
||||||
|
c-0.4-0.188-1.571-0.749-1.812-0.854c-0.047-0.021-0.102-0.041-0.136-0.042c-0.03-0.002-0.061,0.012-0.074,0.044
|
||||||
|
c-0.018,0.045-0.024,0.101-0.027,0.148c-0.003,0.034-0.009,0.063-0.034,0.062c-0.03-0.002-0.035-0.036-0.031-0.106
|
||||||
|
c0.012-0.188,0.033-0.343,0.036-0.391c0.004-0.063,0.004-0.24,0.011-0.353c0.003-0.048,0.013-0.078,0.039-0.076
|
||||||
|
c0.026,0.001,0.032,0.024,0.029,0.062C3.935,21.311,3.93,21.389,3.97,21.46C4,21.51,4.06,21.571,4.293,21.687
|
||||||
|
c0.342,0.168,0.537,0.281,0.983,0.513c0.53,0.274,0.926,0.476,1.106,0.569c0.21,0.11,0.27,0.137,0.266,0.188
|
||||||
|
c-0.003,0.048-0.058,0.067-0.236,0.127L4.071,23.877z M5.128,20.567c-0.556-0.12-0.657-0.143-0.776-0.16
|
||||||
|
c-0.126-0.019-0.191-0.002-0.242,0.113c-0.014,0.026-0.031,0.089-0.042,0.14c-0.009,0.041-0.021,0.062-0.046,0.055
|
||||||
|
c-0.026-0.006-0.027-0.032-0.017-0.087c0.021-0.099,0.047-0.204,0.067-0.295c0.024-0.095,0.045-0.178,0.057-0.229
|
||||||
|
c0.025-0.117,0.183-0.846,0.193-0.916c0.007-0.071,0.013-0.131,0.012-0.162c0-0.02-0.006-0.043-0.002-0.062
|
||||||
|
c0.004-0.018,0.02-0.019,0.038-0.015c0.025,0.005,0.065,0.033,0.231,0.08c0.036,0.012,0.194,0.054,0.236,0.07
|
||||||
|
c0.019,0.008,0.038,0.02,0.032,0.045c-0.005,0.024-0.024,0.028-0.058,0.021C4.787,19.16,4.724,19.15,4.676,19.16
|
||||||
|
c-0.07,0.011-0.122,0.039-0.185,0.216c-0.021,0.062-0.11,0.443-0.126,0.518c-0.004,0.019,0.005,0.027,0.031,0.032l0.925,0.199
|
||||||
|
c0.025,0.005,0.041,0.005,0.045-0.017c0.018-0.081,0.108-0.501,0.119-0.587c0.011-0.089,0.012-0.146-0.018-0.188
|
||||||
|
c-0.023-0.032-0.038-0.051-0.034-0.068c0.003-0.016,0.013-0.024,0.034-0.021c0.022,0.005,0.078,0.032,0.262,0.087
|
||||||
|
c0.072,0.02,0.216,0.062,0.242,0.067C6,19.405,6.04,19.414,6.033,19.447c-0.005,0.026-0.021,0.03-0.04,0.026
|
||||||
|
c-0.037-0.005-0.084-0.015-0.135-0.014c-0.077,0.002-0.144,0.042-0.188,0.174c-0.021,0.068-0.104,0.43-0.123,0.518
|
||||||
|
c-0.004,0.018,0.011,0.024,0.032,0.03l0.289,0.062c0.125,0.027,0.46,0.104,0.567,0.122c0.254,0.047,0.32,0,0.401-0.373
|
||||||
|
c0.021-0.095,0.054-0.249,0.03-0.353c-0.022-0.104-0.091-0.165-0.235-0.224C6.594,19.4,6.581,19.39,6.587,19.364
|
||||||
|
c0.008-0.029,0.036-0.023,0.072-0.015c0.084,0.018,0.327,0.101,0.396,0.135c0.089,0.046,0.083,0.079,0.052,0.218
|
||||||
|
c-0.06,0.274-0.109,0.474-0.146,0.63c-0.041,0.156-0.068,0.27-0.093,0.378c-0.009,0.04-0.026,0.121-0.041,0.209
|
||||||
|
C6.808,21.003,6.796,21.1,6.78,21.173c-0.01,0.049-0.026,0.071-0.052,0.065c-0.019-0.004-0.026-0.021-0.018-0.065
|
||||||
|
c0.013-0.055,0.015-0.1,0.015-0.135c0.001-0.076-0.075-0.112-0.179-0.148c-0.149-0.052-0.438-0.113-0.774-0.187L5.128,20.567z
|
||||||
|
M5.822,17.875c-0.53-0.205-0.628-0.243-0.743-0.278c-0.121-0.039-0.188-0.032-0.257,0.072c-0.018,0.025-0.044,0.083-0.063,0.132
|
||||||
|
c-0.015,0.038-0.029,0.058-0.054,0.048c-0.023-0.01-0.021-0.037-0.002-0.09c0.072-0.185,0.167-0.399,0.195-0.474
|
||||||
|
c0.046-0.118,0.138-0.388,0.18-0.496c0.085-0.22,0.196-0.446,0.398-0.584c0.104-0.072,0.336-0.143,0.569-0.052
|
||||||
|
c0.259,0.1,0.454,0.299,0.604,0.763c0.511-0.16,0.915-0.28,1.21-0.401c0.278-0.117,0.357-0.251,0.389-0.3
|
||||||
|
c0.021-0.035,0.038-0.065,0.048-0.094c0.011-0.028,0.026-0.038,0.044-0.031c0.028,0.012,0.025,0.038,0.01,0.08l-0.128,0.332
|
||||||
|
c-0.075,0.195-0.126,0.276-0.21,0.349c-0.139,0.118-0.354,0.188-0.698,0.279c-0.246,0.065-0.545,0.135-0.615,0.159
|
||||||
|
c-0.027,0.009-0.039,0.029-0.048,0.053l-0.125,0.302c-0.007,0.018-0.004,0.03,0.017,0.039l0.049,0.019
|
||||||
|
c0.325,0.125,0.601,0.232,0.754,0.271C7.45,18,7.535,18.009,7.59,17.91c0.027-0.05,0.064-0.124,0.08-0.166
|
||||||
|
c0.012-0.028,0.027-0.038,0.044-0.031c0.024,0.011,0.025,0.038,0.009,0.083c-0.079,0.202-0.188,0.457-0.209,0.51
|
||||||
|
c-0.025,0.065-0.101,0.289-0.154,0.43C7.341,18.779,7.321,18.8,7.297,18.79c-0.018-0.007-0.021-0.023-0.006-0.065
|
||||||
|
c0.021-0.054,0.029-0.098,0.035-0.132c0.013-0.074-0.057-0.123-0.153-0.176c-0.14-0.074-0.415-0.181-0.735-0.305L5.822,17.875z
|
||||||
|
M6.257,17.57c0.038,0.015,0.056,0.013,0.075-0.007c0.053-0.063,0.104-0.164,0.138-0.251c0.054-0.141,0.058-0.19,0.036-0.271
|
||||||
|
c-0.035-0.134-0.157-0.298-0.443-0.408c-0.495-0.191-0.766,0.081-0.846,0.287c-0.033,0.087-0.055,0.151-0.058,0.19
|
||||||
|
c-0.002,0.027,0.009,0.04,0.037,0.05L6.257,17.57z M8.459,16.014c-0.053,0.038-0.074,0.04-0.146-0.001
|
||||||
|
c-0.18-0.103-0.367-0.227-0.417-0.259c-0.047-0.03-0.077-0.061-0.062-0.087c0.018-0.028,0.048-0.016,0.074-0.001
|
||||||
|
c0.042,0.024,0.118,0.05,0.183,0.064c0.281,0.064,0.479-0.077,0.594-0.278c0.166-0.293,0.049-0.549-0.123-0.646
|
||||||
|
C8.4,14.716,8.223,14.68,7.858,14.83l-0.202,0.083c-0.483,0.199-0.781,0.193-1.044,0.044c-0.357-0.204-0.445-0.647-0.188-1.101
|
||||||
|
c0.12-0.212,0.23-0.33,0.302-0.401c0.022-0.024,0.042-0.036,0.064-0.022c0.042,0.023,0.129,0.091,0.383,0.233
|
||||||
|
c0.072,0.041,0.093,0.065,0.078,0.091c-0.013,0.023-0.038,0.021-0.077,0c-0.028-0.017-0.139-0.058-0.263-0.038
|
||||||
|
c-0.089,0.015-0.241,0.054-0.361,0.265C6.413,14.224,6.47,14.45,6.665,14.56c0.15,0.085,0.307,0.074,0.664-0.079l0.12-0.052
|
||||||
|
c0.521-0.227,0.823-0.238,1.133-0.062c0.188,0.107,0.372,0.306,0.393,0.627c0.012,0.222-0.062,0.421-0.16,0.593
|
||||||
|
C8.706,15.776,8.6,15.909,8.459,16.014z M8.373,12.925c-0.459-0.335-0.544-0.396-0.645-0.462c-0.107-0.067-0.182-0.075-0.244-0.021
|
||||||
|
c-0.034,0.026-0.065,0.063-0.091,0.1c-0.022,0.03-0.042,0.044-0.066,0.026c-0.018-0.013-0.006-0.042,0.029-0.09
|
||||||
|
c0.084-0.115,0.231-0.299,0.296-0.387c0.055-0.075,0.176-0.26,0.26-0.375c0.029-0.039,0.052-0.059,0.07-0.046
|
||||||
|
c0.023,0.019,0.017,0.041-0.005,0.071c-0.022,0.03-0.037,0.057-0.058,0.097c-0.044,0.094-0.011,0.154,0.092,0.237
|
||||||
|
c0.092,0.077,0.177,0.139,0.636,0.474l0.532,0.389c0.293,0.214,0.531,0.388,0.671,0.471c0.088,0.05,0.157,0.069,0.235-0.013
|
||||||
|
c0.037-0.038,0.093-0.101,0.125-0.146c0.024-0.033,0.044-0.042,0.061-0.031c0.021,0.017,0.012,0.047-0.013,0.08
|
||||||
|
c-0.146,0.199-0.294,0.384-0.354,0.466c-0.051,0.068-0.18,0.267-0.269,0.387c-0.029,0.04-0.054,0.054-0.074,0.039
|
||||||
|
c-0.016-0.011-0.017-0.03,0.011-0.066c0.033-0.045,0.053-0.086,0.067-0.118c0.031-0.068-0.007-0.125-0.087-0.197
|
||||||
|
c-0.116-0.108-0.354-0.282-0.648-0.496L8.373,12.925z M9.295,10.406l-0.372,0.415c-0.144,0.163-0.19,0.241-0.17,0.338
|
||||||
|
c0.016,0.065,0.035,0.11,0.052,0.136c0.017,0.026,0.02,0.044,0.004,0.062c-0.019,0.019-0.037,0.012-0.066-0.018
|
||||||
|
c-0.044-0.041-0.242-0.326-0.259-0.353c-0.028-0.042-0.036-0.065-0.021-0.081c0.021-0.022,0.073-0.023,0.144-0.086
|
||||||
|
c0.083-0.071,0.186-0.169,0.271-0.259l1.018-1.076c0.083-0.087,0.131-0.154,0.164-0.2c0.029-0.049,0.045-0.076,0.056-0.086
|
||||||
|
c0.018-0.019,0.037-0.006,0.078,0.032c0.057,0.054,0.237,0.238,0.308,0.306c0.024,0.028,0.036,0.049,0.021,0.065
|
||||||
|
c-0.021,0.021-0.039,0.014-0.083-0.021l-0.032-0.024c-0.076-0.062-0.221-0.061-0.456,0.177l-0.332,0.335l1.115,1.053
|
||||||
|
c0.25,0.236,0.465,0.439,0.595,0.536c0.083,0.062,0.159,0.104,0.247,0.032c0.042-0.033,0.104-0.087,0.142-0.128
|
||||||
|
c0.028-0.03,0.05-0.036,0.062-0.023c0.02,0.019,0.007,0.047-0.021,0.077c-0.169,0.18-0.339,0.343-0.41,0.419
|
||||||
|
c-0.06,0.062-0.214,0.242-0.315,0.351c-0.033,0.035-0.06,0.047-0.079,0.028c-0.014-0.013-0.012-0.031,0.02-0.063
|
||||||
|
c0.038-0.041,0.063-0.078,0.081-0.108c0.04-0.065-0.004-0.138-0.074-0.22c-0.103-0.122-0.316-0.324-0.566-0.562L9.295,10.406z
|
||||||
|
M12.134,9.947c-0.015,0.012-0.016,0.021-0.008,0.044l0.176,0.544c0.029,0.096,0.07,0.178,0.099,0.213
|
||||||
|
c0.042,0.053,0.099,0.069,0.188-0.003l0.044-0.035c0.035-0.027,0.049-0.028,0.062-0.012c0.019,0.023,0.006,0.043-0.026,0.069
|
||||||
|
c-0.094,0.074-0.227,0.166-0.316,0.237c-0.032,0.026-0.187,0.163-0.338,0.284c-0.038,0.03-0.061,0.039-0.079,0.016
|
||||||
|
c-0.015-0.019-0.007-0.033,0.017-0.052c0.026-0.021,0.065-0.057,0.086-0.078c0.121-0.125,0.097-0.269,0.039-0.461l-0.73-2.421
|
||||||
|
c-0.032-0.112-0.041-0.159-0.012-0.182c0.026-0.021,0.065-0.009,0.147,0.035c0.199,0.104,1.618,0.924,2.159,1.22
|
||||||
|
c0.32,0.174,0.438,0.148,0.512,0.113c0.051-0.026,0.097-0.059,0.132-0.086c0.023-0.02,0.041-0.027,0.057-0.008
|
||||||
|
c0.02,0.023-0.003,0.051-0.11,0.138c-0.105,0.084-0.319,0.254-0.558,0.435c-0.055,0.039-0.09,0.067-0.105,0.047
|
||||||
|
c-0.014-0.018-0.007-0.032,0.02-0.059c0.017-0.022,0.016-0.065-0.026-0.088l-0.729-0.434c-0.017-0.01-0.031-0.009-0.045,0.003
|
||||||
|
L12.134,9.947z M12.614,9.325c0.014-0.012,0.01-0.022,0-0.029l-0.839-0.513c-0.012-0.009-0.027-0.021-0.036-0.015
|
||||||
|
c-0.009,0.007-0.003,0.025,0.003,0.041l0.305,0.934c0.008,0.014,0.018,0.021,0.028,0.011L12.614,9.325z M14.557,9.301
|
||||||
|
c-0.064,0.009-0.084-0.002-0.126-0.072c-0.104-0.178-0.206-0.378-0.233-0.432c-0.025-0.05-0.037-0.091-0.011-0.105
|
||||||
|
c0.028-0.018,0.049,0.01,0.063,0.036c0.024,0.042,0.078,0.102,0.126,0.146c0.212,0.196,0.453,0.17,0.652,0.052
|
||||||
|
c0.29-0.172,0.315-0.451,0.214-0.623c-0.093-0.156-0.229-0.276-0.62-0.327l-0.217-0.028c-0.518-0.067-0.773-0.22-0.928-0.48
|
||||||
|
c-0.21-0.354-0.064-0.782,0.383-1.048c0.209-0.124,0.363-0.172,0.46-0.199c0.033-0.011,0.055-0.011,0.068,0.013
|
||||||
|
c0.024,0.042,0.066,0.143,0.216,0.394c0.042,0.071,0.047,0.104,0.021,0.118c-0.021,0.013-0.043,0-0.065-0.039
|
||||||
|
c-0.018-0.029-0.093-0.119-0.209-0.163c-0.084-0.032-0.235-0.074-0.444,0.05c-0.238,0.142-0.301,0.365-0.187,0.559
|
||||||
|
c0.088,0.147,0.229,0.217,0.614,0.261l0.13,0.014c0.562,0.062,0.833,0.202,1.015,0.508c0.109,0.187,0.171,0.45,0.028,0.738
|
||||||
|
c-0.1,0.198-0.263,0.333-0.433,0.436C14.89,9.217,14.731,9.28,14.557,9.301z M34.089,6.584L33.6,6.314
|
||||||
|
c-0.191-0.104-0.278-0.133-0.368-0.09c-0.062,0.028-0.101,0.059-0.121,0.081c-0.022,0.021-0.039,0.028-0.06,0.018
|
||||||
|
c-0.021-0.013-0.02-0.033,0.002-0.069c0.029-0.051,0.264-0.309,0.285-0.332c0.034-0.036,0.056-0.05,0.075-0.038
|
||||||
|
c0.026,0.016,0.039,0.066,0.116,0.12c0.088,0.064,0.207,0.144,0.312,0.205l1.278,0.75c0.104,0.062,0.18,0.093,0.232,0.115
|
||||||
|
c0.054,0.019,0.084,0.027,0.097,0.035c0.022,0.014,0.015,0.035-0.014,0.083c-0.04,0.067-0.181,0.285-0.229,0.369
|
||||||
|
c-0.021,0.03-0.04,0.046-0.06,0.035c-0.025-0.016-0.022-0.035,0.003-0.086l0.018-0.036c0.042-0.089,0.008-0.229-0.274-0.404
|
||||||
|
l-0.401-0.25l-0.776,1.323c-0.174,0.297-0.323,0.552-0.39,0.699c-0.043,0.097-0.064,0.179,0.024,0.248
|
||||||
|
c0.042,0.033,0.108,0.081,0.156,0.109c0.036,0.021,0.047,0.04,0.037,0.056c-0.013,0.022-0.044,0.018-0.08-0.004
|
||||||
|
C33.25,9.129,33.053,9,32.962,8.947c-0.073-0.043-0.282-0.153-0.412-0.229c-0.042-0.023-0.059-0.048-0.046-0.069
|
||||||
|
c0.01-0.017,0.028-0.019,0.067,0.004c0.048,0.029,0.091,0.045,0.124,0.055c0.072,0.025,0.133-0.034,0.197-0.122
|
||||||
|
c0.096-0.126,0.245-0.381,0.42-0.678L34.089,6.584z M35.774,8.711c0.371-0.431,0.438-0.511,0.511-0.605
|
||||||
|
c0.077-0.102,0.093-0.166,0.017-0.267c-0.018-0.024-0.062-0.069-0.104-0.104c-0.031-0.027-0.043-0.047-0.026-0.067
|
||||||
|
c0.018-0.02,0.042-0.009,0.084,0.028c0.15,0.129,0.322,0.291,0.407,0.364c0.068,0.058,0.254,0.204,0.356,0.291
|
||||||
|
c0.042,0.037,0.058,0.059,0.04,0.079c-0.018,0.02-0.039,0.011-0.067-0.014c-0.031-0.026-0.051-0.039-0.09-0.062
|
||||||
|
c-0.089-0.053-0.153-0.022-0.244,0.071c-0.084,0.087-0.152,0.166-0.521,0.598l-0.34,0.396c-0.353,0.412-0.419,0.655-0.378,0.889
|
||||||
|
c0.038,0.214,0.158,0.338,0.255,0.421c0.125,0.106,0.312,0.198,0.523,0.183c0.29-0.021,0.518-0.271,0.769-0.562l0.306-0.354
|
||||||
|
c0.37-0.432,0.438-0.511,0.511-0.605c0.077-0.103,0.093-0.167,0.017-0.268c-0.018-0.024-0.062-0.068-0.095-0.097
|
||||||
|
C37.673,9,37.661,8.979,37.678,8.96c0.017-0.02,0.042-0.009,0.08,0.025c0.146,0.124,0.316,0.286,0.319,0.289
|
||||||
|
c0.034,0.028,0.22,0.174,0.331,0.269c0.04,0.034,0.055,0.058,0.038,0.077c-0.017,0.021-0.039,0.011-0.073-0.018
|
||||||
|
c-0.031-0.027-0.05-0.039-0.089-0.062c-0.089-0.052-0.153-0.022-0.244,0.071c-0.083,0.086-0.151,0.166-0.521,0.597l-0.262,0.303
|
||||||
|
c-0.27,0.315-0.589,0.623-1.002,0.623c-0.35,0-0.581-0.144-0.766-0.302c-0.15-0.129-0.416-0.372-0.462-0.737
|
||||||
|
c-0.033-0.254,0.04-0.557,0.393-0.969L35.774,8.711z M38.61,11.381c0.443-0.355,0.523-0.422,0.613-0.503
|
||||||
|
c0.094-0.085,0.122-0.146,0.064-0.259c-0.013-0.028-0.05-0.08-0.083-0.121c-0.025-0.032-0.034-0.054-0.014-0.071
|
||||||
|
c0.021-0.016,0.043,0,0.078,0.043c0.125,0.154,0.263,0.346,0.312,0.406c0.079,0.1,0.268,0.313,0.339,0.404
|
||||||
|
c0.148,0.184,0.288,0.393,0.308,0.637c0.01,0.126-0.045,0.362-0.24,0.521c-0.216,0.173-0.486,0.242-0.962,0.141
|
||||||
|
c-0.117,0.522-0.215,0.932-0.257,1.249c-0.038,0.299,0.038,0.436,0.063,0.485c0.021,0.037,0.038,0.064,0.058,0.089
|
||||||
|
c0.019,0.022,0.02,0.042,0.005,0.054c-0.023,0.02-0.045,0.003-0.074-0.032l-0.223-0.275c-0.132-0.163-0.176-0.248-0.195-0.356
|
||||||
|
c-0.034-0.179,0.014-0.399,0.106-0.744c0.066-0.246,0.156-0.539,0.171-0.611c0.006-0.029-0.007-0.05-0.022-0.069l-0.198-0.258
|
||||||
|
c-0.012-0.016-0.023-0.02-0.042-0.005l-0.041,0.032c-0.271,0.219-0.501,0.403-0.611,0.517c-0.077,0.077-0.127,0.146-0.068,0.243
|
||||||
|
c0.029,0.048,0.075,0.117,0.103,0.151c0.02,0.023,0.021,0.042,0.006,0.055c-0.021,0.016-0.046,0.003-0.076-0.035
|
||||||
|
c-0.137-0.169-0.301-0.393-0.336-0.437c-0.044-0.055-0.201-0.23-0.294-0.348c-0.031-0.038-0.038-0.065-0.019-0.082
|
||||||
|
c0.016-0.012,0.033-0.007,0.062,0.028c0.034,0.043,0.068,0.073,0.096,0.096c0.059,0.049,0.135,0.012,0.229-0.044
|
||||||
|
c0.135-0.084,0.364-0.271,0.633-0.485L38.61,11.381z M38.656,11.91c-0.032,0.026-0.039,0.041-0.031,0.069
|
||||||
|
c0.029,0.078,0.09,0.172,0.149,0.245c0.094,0.116,0.136,0.145,0.216,0.167c0.134,0.035,0.336,0.013,0.575-0.181
|
||||||
|
c0.414-0.333,0.312-0.703,0.174-0.875c-0.059-0.072-0.104-0.123-0.136-0.145c-0.022-0.017-0.038-0.012-0.062,0.006L38.656,11.91z
|
||||||
|
M40.591,14.113c0.49-0.289,0.58-0.342,0.68-0.409c0.104-0.071,0.142-0.136,0.116-0.216c-0.012-0.041-0.033-0.084-0.056-0.123
|
||||||
|
c-0.02-0.032-0.023-0.056,0.001-0.071c0.021-0.011,0.041,0.012,0.072,0.062c0.072,0.123,0.182,0.331,0.237,0.424
|
||||||
|
c0.047,0.081,0.169,0.265,0.241,0.387c0.024,0.042,0.033,0.072,0.015,0.083c-0.025,0.016-0.043,0-0.062-0.033
|
||||||
|
c-0.019-0.032-0.038-0.056-0.065-0.091c-0.067-0.078-0.139-0.07-0.254-0.011c-0.107,0.054-0.197,0.108-0.688,0.396l-0.566,0.334
|
||||||
|
c-0.312,0.185-0.567,0.335-0.698,0.43c-0.081,0.061-0.126,0.118-0.082,0.222c0.021,0.049,0.056,0.123,0.084,0.172
|
||||||
|
c0.021,0.035,0.021,0.058,0.004,0.066c-0.022,0.014-0.047-0.007-0.068-0.042c-0.125-0.212-0.234-0.421-0.287-0.508
|
||||||
|
c-0.043-0.074-0.173-0.271-0.249-0.4c-0.024-0.042-0.027-0.07-0.006-0.083c0.017-0.009,0.033-0.002,0.057,0.036
|
||||||
|
c0.028,0.049,0.058,0.083,0.082,0.109c0.051,0.057,0.116,0.043,0.216-0.002c0.145-0.063,0.398-0.214,0.712-0.397L40.591,14.113z
|
||||||
|
M40.314,16.493c0.157-0.461,0.507-0.711,0.842-0.85c0.235-0.098,0.672-0.193,1.114,0.013c0.331,0.154,0.604,0.425,0.817,0.94
|
||||||
|
c0.089,0.214,0.128,0.348,0.174,0.515c0.036,0.139,0.052,0.262,0.084,0.37c0.012,0.039,0.001,0.06-0.021,0.068
|
||||||
|
c-0.028,0.012-0.073,0.019-0.197,0.062c-0.116,0.041-0.306,0.123-0.378,0.144c-0.053,0.019-0.084,0.022-0.097-0.008
|
||||||
|
c-0.011-0.028,0.014-0.046,0.062-0.065c0.105-0.048,0.208-0.144,0.263-0.267c0.073-0.164,0.056-0.472-0.076-0.79
|
||||||
|
c-0.125-0.3-0.282-0.466-0.483-0.561c-0.335-0.156-0.69-0.086-1.025,0.053c-0.822,0.34-0.972,1.134-0.78,1.597
|
||||||
|
c0.127,0.309,0.239,0.48,0.455,0.554c0.09,0.03,0.209,0.034,0.275,0.022c0.061-0.012,0.076-0.011,0.089,0.017
|
||||||
|
c0.01,0.023-0.012,0.042-0.039,0.053c-0.041,0.017-0.359,0.1-0.491,0.114c-0.066,0.007-0.089,0-0.137-0.05
|
||||||
|
c-0.113-0.114-0.246-0.367-0.338-0.589C40.232,17.361,40.171,16.92,40.314,16.493z M42.775,19.295
|
||||||
|
c0.555-0.125,0.657-0.146,0.771-0.181c0.123-0.035,0.175-0.077,0.174-0.204c0-0.031-0.01-0.094-0.021-0.145
|
||||||
|
c-0.009-0.04-0.007-0.064,0.019-0.069c0.026-0.006,0.039,0.018,0.052,0.073c0.021,0.099,0.042,0.205,0.062,0.296
|
||||||
|
c0.019,0.096,0.033,0.181,0.044,0.23c0.026,0.117,0.189,0.844,0.208,0.912c0.023,0.068,0.043,0.125,0.058,0.152
|
||||||
|
c0.008,0.018,0.023,0.037,0.027,0.056s-0.01,0.025-0.027,0.029c-0.025,0.005-0.073-0.003-0.244,0.023
|
||||||
|
c-0.038,0.005-0.2,0.033-0.245,0.036c-0.02,0-0.042-0.002-0.048-0.026c-0.006-0.025,0.011-0.037,0.043-0.044
|
||||||
|
c0.025-0.005,0.087-0.023,0.126-0.052c0.06-0.041,0.096-0.087,0.077-0.273c-0.006-0.063-0.085-0.448-0.103-0.521
|
||||||
|
c-0.004-0.018-0.017-0.022-0.042-0.017l-0.923,0.208c-0.026,0.006-0.04,0.013-0.035,0.034c0.019,0.081,0.112,0.5,0.14,0.583
|
||||||
|
c0.026,0.086,0.05,0.138,0.094,0.163c0.035,0.019,0.056,0.028,0.061,0.047c0.003,0.015-0.001,0.026-0.022,0.032
|
||||||
|
c-0.022,0.005-0.083,0.003-0.273,0.03c-0.074,0.014-0.223,0.035-0.248,0.041c-0.028,0.006-0.068,0.017-0.077-0.018
|
||||||
|
c-0.006-0.025,0.008-0.036,0.025-0.04c0.036-0.012,0.083-0.022,0.128-0.044c0.069-0.035,0.112-0.099,0.096-0.236
|
||||||
|
c-0.008-0.071-0.086-0.434-0.104-0.521c-0.004-0.019-0.021-0.02-0.042-0.015l-0.288,0.065c-0.124,0.028-0.461,0.1-0.566,0.127
|
||||||
|
c-0.25,0.062-0.292,0.134-0.208,0.507c0.021,0.095,0.057,0.248,0.121,0.333c0.065,0.085,0.152,0.112,0.308,0.104
|
||||||
|
c0.042-0.001,0.058,0.002,0.063,0.027c0.006,0.029-0.022,0.036-0.06,0.044c-0.084,0.02-0.338,0.045-0.416,0.043
|
||||||
|
c-0.102-0.004-0.108-0.037-0.14-0.176c-0.062-0.272-0.099-0.476-0.131-0.634c-0.027-0.158-0.05-0.272-0.074-0.382
|
||||||
|
c-0.009-0.04-0.026-0.121-0.05-0.207c-0.019-0.084-0.047-0.177-0.063-0.25c-0.012-0.047-0.006-0.075,0.02-0.081
|
||||||
|
c0.019-0.004,0.033,0.008,0.042,0.052c0.012,0.055,0.029,0.097,0.044,0.128c0.031,0.069,0.115,0.07,0.226,0.062
|
||||||
|
c0.156-0.017,0.445-0.081,0.78-0.156L42.775,19.295z M42.087,22.487c-0.292,0.018-0.387,0.07-0.41,0.146
|
||||||
|
c-0.021,0.064-0.021,0.137-0.02,0.195c0.001,0.041-0.005,0.064-0.027,0.064c-0.029,0.001-0.039-0.032-0.041-0.084
|
||||||
|
c-0.009-0.243-0.003-0.394-0.005-0.465c-0.001-0.033-0.019-0.209-0.024-0.402c-0.002-0.048,0.001-0.083,0.033-0.083
|
||||||
|
c0.022-0.001,0.031,0.021,0.032,0.059c0.002,0.049,0.008,0.116,0.024,0.167c0.034,0.097,0.143,0.107,0.468,0.101l2.214-0.044
|
||||||
|
c0.075-0.003,0.128,0.007,0.129,0.044c0.001,0.041-0.046,0.075-0.11,0.143c-0.048,0.046-0.637,0.636-1.159,1.201
|
||||||
|
c-0.245,0.263-0.767,0.787-0.823,0.852v0.02l1.677-0.098c0.229-0.012,0.298-0.048,0.332-0.139c0.021-0.057,0.018-0.146,0.016-0.199
|
||||||
|
c-0.001-0.045,0.009-0.061,0.031-0.061c0.03-0.001,0.035,0.04,0.037,0.095c0.007,0.194,0.002,0.375,0.005,0.453
|
||||||
|
c0.001,0.041,0.019,0.183,0.024,0.366c0.001,0.049,0,0.086-0.031,0.087c-0.021,0.001-0.034-0.021-0.036-0.066
|
||||||
|
c-0.001-0.038-0.002-0.066-0.015-0.111c-0.034-0.104-0.113-0.132-0.323-0.127l-2.359,0.042c-0.082,0.003-0.116-0.011-0.118-0.044
|
||||||
|
c-0.001-0.041,0.038-0.088,0.078-0.13c0.216-0.243,0.688-0.738,1.06-1.142c0.39-0.421,0.842-0.853,0.911-0.923v-0.011L42.087,22.487
|
||||||
|
z M41.585,25.938c0.035-0.057,0.056-0.064,0.137-0.054c0.203,0.03,0.424,0.076,0.481,0.09c0.055,0.012,0.094,0.027,0.09,0.058
|
||||||
|
c-0.004,0.034-0.038,0.032-0.067,0.028c-0.049-0.008-0.128-0.003-0.194,0.006c-0.286,0.042-0.417,0.246-0.45,0.476
|
||||||
|
c-0.048,0.333,0.154,0.528,0.352,0.558c0.182,0.025,0.359-0.006,0.645-0.277l0.158-0.151c0.377-0.36,0.656-0.465,0.956-0.422
|
||||||
|
c0.407,0.06,0.649,0.441,0.577,0.956c-0.035,0.24-0.095,0.392-0.135,0.483c-0.012,0.031-0.025,0.049-0.052,0.045
|
||||||
|
c-0.048-0.007-0.152-0.037-0.441-0.078c-0.082-0.012-0.109-0.027-0.105-0.057c0.004-0.026,0.027-0.034,0.072-0.027
|
||||||
|
c0.033,0.004,0.149,0.002,0.258-0.062c0.078-0.046,0.205-0.137,0.239-0.378c0.04-0.272-0.097-0.463-0.318-0.495
|
||||||
|
c-0.17-0.023-0.312,0.042-0.589,0.316l-0.093,0.093c-0.401,0.398-0.68,0.521-1.031,0.472c-0.215-0.03-0.458-0.148-0.594-0.44
|
||||||
|
c-0.092-0.202-0.097-0.414-0.067-0.61C41.442,26.249,41.492,26.087,41.585,25.938z M42.705,29.112
|
||||||
|
c0.547,0.154,0.648,0.183,0.766,0.208c0.125,0.027,0.195,0.009,0.235-0.065c0.022-0.036,0.039-0.082,0.051-0.125
|
||||||
|
c0.011-0.036,0.023-0.056,0.053-0.048c0.021,0.007,0.021,0.037,0.004,0.095c-0.039,0.138-0.113,0.36-0.144,0.466
|
||||||
|
c-0.024,0.09-0.073,0.305-0.112,0.44c-0.014,0.047-0.028,0.074-0.051,0.068c-0.028-0.009-0.029-0.032-0.02-0.068
|
||||||
|
s0.015-0.064,0.02-0.11c0.01-0.103-0.044-0.147-0.168-0.19c-0.112-0.04-0.213-0.068-0.76-0.223l-0.633-0.181
|
||||||
|
c-0.349-0.099-0.633-0.179-0.792-0.208c-0.1-0.017-0.172-0.01-0.217,0.095c-0.021,0.049-0.051,0.126-0.066,0.18
|
||||||
|
c-0.011,0.04-0.026,0.055-0.045,0.05c-0.025-0.007-0.028-0.039-0.017-0.079c0.066-0.236,0.142-0.461,0.169-0.559
|
||||||
|
c0.022-0.082,0.077-0.312,0.118-0.456c0.013-0.047,0.03-0.068,0.056-0.062c0.018,0.005,0.025,0.022,0.013,0.066
|
||||||
|
c-0.016,0.053-0.021,0.099-0.021,0.133c-0.006,0.076,0.049,0.115,0.149,0.155c0.146,0.061,0.431,0.141,0.779,0.239L42.705,29.112z
|
||||||
|
M40.502,30.431c0.048-0.046,0.069-0.048,0.146-0.016c0.188,0.081,0.389,0.186,0.441,0.211c0.051,0.026,0.083,0.054,0.072,0.08
|
||||||
|
c-0.013,0.031-0.046,0.021-0.073,0.009c-0.044-0.019-0.123-0.035-0.188-0.044c-0.287-0.033-0.467,0.128-0.56,0.342
|
||||||
|
c-0.134,0.309,0.011,0.55,0.192,0.629c0.168,0.071,0.348,0.09,0.694-0.101l0.191-0.104c0.458-0.25,0.755-0.277,1.033-0.156
|
||||||
|
c0.377,0.163,0.512,0.596,0.306,1.072c-0.097,0.224-0.192,0.353-0.256,0.432c-0.02,0.028-0.038,0.041-0.062,0.03
|
||||||
|
c-0.044-0.021-0.139-0.076-0.406-0.192c-0.075-0.032-0.099-0.055-0.086-0.082c0.01-0.022,0.035-0.024,0.077-0.007
|
||||||
|
c0.031,0.013,0.145,0.042,0.265,0.009c0.088-0.022,0.233-0.079,0.33-0.302c0.11-0.254,0.029-0.473-0.177-0.562
|
||||||
|
c-0.158-0.068-0.312-0.041-0.651,0.149l-0.113,0.064c-0.492,0.28-0.792,0.325-1.119,0.185c-0.199-0.086-0.403-0.264-0.458-0.58
|
||||||
|
c-0.036-0.22,0.017-0.425,0.095-0.606C40.282,30.694,40.373,30.55,40.502,30.431z M13.791,41.773c-0.024,0-0.056-0.002-0.114-0.02
|
||||||
|
c-0.053-0.015-0.07-0.058-0.09-0.186l-0.189-1.25c-0.009-0.048-0.034-0.077-0.064-0.079c-0.029,0.001-0.052,0.024-0.069,0.06
|
||||||
|
l-0.548,1.112l-0.546-1.098c-0.026-0.057-0.05-0.074-0.08-0.074c-0.029,0.001-0.052,0.029-0.057,0.066l-0.207,1.321
|
||||||
|
c-0.009,0.067-0.032,0.141-0.071,0.142c-0.032,0.004-0.046,0.004-0.062,0.004c-0.025,0-0.049,0.012-0.05,0.037
|
||||||
|
c0,0.032,0.033,0.04,0.055,0.04c0.068,0,0.168-0.008,0.208-0.008c0.037,0,0.132,0.008,0.221,0.008c0.029,0,0.065-0.005,0.067-0.04
|
||||||
|
c-0.003-0.027-0.026-0.036-0.05-0.037c-0.018,0-0.035-0.002-0.07-0.009c-0.035-0.009-0.05-0.017-0.051-0.044
|
||||||
|
c0-0.031,0.002-0.058,0.007-0.093l0.094-0.746c0.07,0.145,0.175,0.359,0.19,0.395c0.024,0.06,0.19,0.367,0.241,0.461
|
||||||
|
c0.034,0.061,0.054,0.105,0.099,0.111c0.04-0.004,0.051-0.031,0.099-0.124l0.432-0.866l0.109,0.832
|
||||||
|
c0.002,0.018,0.003,0.032,0.003,0.043c0,0.023-0.003,0.022-0.002,0.024c-0.015,0.006-0.033,0.015-0.035,0.037
|
||||||
|
c0.003,0.029,0.029,0.039,0.078,0.041c0.084,0.005,0.382,0.015,0.436,0.015c0.031-0.001,0.066-0.003,0.071-0.04
|
||||||
|
C13.839,41.781,13.812,41.773,13.791,41.773z M15.5,40.521c-0.234-0.237-0.593-0.235-0.854-0.235c-0.126,0-0.277,0.004-0.341,0.004
|
||||||
|
c-0.06,0-0.193-0.004-0.303-0.004c-0.028,0-0.062,0.002-0.064,0.035c0.001,0.029,0.029,0.039,0.053,0.039
|
||||||
|
c0.03,0,0.066,0.002,0.079,0.006c0.063,0.021,0.071,0.028,0.078,0.095c0.003,0.063,0.003,0.12,0.003,0.429v0.355
|
||||||
|
c0,0.188,0,0.346-0.01,0.43c-0.009,0.061-0.019,0.086-0.05,0.094c-0.018,0.004-0.04,0.006-0.069,0.006
|
||||||
|
c-0.031,0-0.05,0.02-0.05,0.039c0.001,0.028,0.029,0.038,0.058,0.038c0.043,0,0.096-0.002,0.146-0.005
|
||||||
|
c0.051,0,0.097-0.003,0.119-0.003c0.052,0,0.127,0.006,0.21,0.013c0.083,0.005,0.175,0.012,0.251,0.012
|
||||||
|
c0.391,0,0.616-0.145,0.715-0.243c0.121-0.119,0.233-0.315,0.233-0.576C15.703,40.802,15.607,40.629,15.5,40.521z M15.368,41.105
|
||||||
|
c0,0.207-0.042,0.382-0.172,0.488c-0.123,0.103-0.258,0.135-0.446,0.135c-0.162,0.001-0.24-0.044-0.26-0.073
|
||||||
|
c-0.011-0.012-0.02-0.086-0.021-0.134c-0.003-0.04-0.006-0.195-0.006-0.409v-0.255c0-0.159,0-0.326,0.003-0.396
|
||||||
|
c0.003-0.021,0-0.018,0.016-0.024c0.009-0.007,0.082-0.015,0.123-0.014c0.163,0,0.386,0.021,0.574,0.188
|
||||||
|
C15.268,40.69,15.368,40.853,15.368,41.105z M17.351,41.434c-0.028-0.001-0.041,0.025-0.042,0.055
|
||||||
|
c-0.006,0.033-0.034,0.09-0.068,0.127c-0.078,0.087-0.173,0.102-0.358,0.102c-0.272-0.001-0.635-0.222-0.636-0.691
|
||||||
|
c0-0.196,0.038-0.386,0.188-0.514c0.091-0.077,0.203-0.11,0.384-0.11c0.189,0,0.327,0.05,0.393,0.114
|
||||||
|
c0.049,0.049,0.074,0.116,0.076,0.177c0,0.023,0.004,0.058,0.039,0.06c0.036-0.003,0.043-0.038,0.045-0.065
|
||||||
|
c0.002-0.041,0.002-0.153,0.007-0.22c0.004-0.07,0.01-0.094,0.01-0.112c0.002-0.021-0.02-0.04-0.047-0.04
|
||||||
|
c-0.061-0.008-0.129-0.022-0.208-0.034c-0.096-0.012-0.176-0.021-0.308-0.021c-0.313,0-0.52,0.083-0.675,0.22
|
||||||
|
c-0.205,0.184-0.251,0.426-0.251,0.566c0,0.198,0.056,0.435,0.268,0.61c0.195,0.164,0.442,0.224,0.73,0.224
|
||||||
|
c0.137,0,0.296-0.012,0.385-0.047c0.037-0.013,0.057-0.033,0.065-0.069c0.021-0.072,0.045-0.245,0.045-0.273
|
||||||
|
C17.391,41.465,17.382,41.437,17.351,41.434z M19.029,41.434c-0.028-0.001-0.041,0.025-0.043,0.055
|
||||||
|
c-0.005,0.033-0.034,0.09-0.067,0.127c-0.079,0.087-0.173,0.102-0.358,0.102c-0.273-0.001-0.636-0.222-0.637-0.691
|
||||||
|
c0-0.196,0.038-0.386,0.188-0.514c0.092-0.077,0.203-0.11,0.384-0.11c0.19,0,0.327,0.05,0.394,0.114
|
||||||
|
c0.049,0.049,0.072,0.116,0.075,0.177c0,0.023,0.006,0.058,0.039,0.06c0.037-0.003,0.043-0.038,0.045-0.065
|
||||||
|
c0.002-0.041,0.002-0.153,0.008-0.22c0.004-0.07,0.009-0.094,0.01-0.112c0.001-0.021-0.021-0.04-0.047-0.04
|
||||||
|
c-0.062-0.008-0.129-0.022-0.208-0.034c-0.097-0.012-0.177-0.021-0.308-0.021c-0.314,0-0.521,0.083-0.676,0.22
|
||||||
|
c-0.205,0.184-0.251,0.426-0.251,0.566c0,0.198,0.056,0.435,0.269,0.61c0.194,0.164,0.441,0.224,0.729,0.224
|
||||||
|
c0.137,0,0.297-0.012,0.385-0.047c0.037-0.013,0.058-0.033,0.064-0.069c0.021-0.072,0.045-0.245,0.046-0.273
|
||||||
|
C19.069,41.465,19.06,41.437,19.029,41.434z M20.707,41.434c-0.027-0.001-0.041,0.025-0.043,0.055
|
||||||
|
c-0.006,0.033-0.034,0.09-0.066,0.127c-0.079,0.087-0.174,0.102-0.359,0.102c-0.272-0.001-0.635-0.222-0.635-0.691
|
||||||
|
c0-0.196,0.037-0.386,0.187-0.514c0.092-0.077,0.203-0.11,0.385-0.11c0.189,0,0.327,0.05,0.394,0.114
|
||||||
|
c0.049,0.049,0.074,0.116,0.076,0.177c0,0.023,0.005,0.058,0.039,0.06c0.037-0.003,0.043-0.038,0.045-0.065
|
||||||
|
c0.002-0.041,0.002-0.153,0.007-0.22c0.005-0.07,0.009-0.094,0.009-0.112c0.002-0.021-0.019-0.04-0.046-0.04
|
||||||
|
c-0.061-0.008-0.129-0.022-0.209-0.034c-0.095-0.012-0.175-0.021-0.307-0.021c-0.314,0-0.521,0.083-0.677,0.22
|
||||||
|
c-0.205,0.184-0.251,0.426-0.251,0.566c0,0.198,0.057,0.435,0.269,0.61c0.194,0.164,0.441,0.224,0.729,0.224
|
||||||
|
c0.138,0,0.297-0.012,0.386-0.047c0.036-0.013,0.057-0.033,0.064-0.069c0.022-0.072,0.045-0.245,0.046-0.273
|
||||||
|
C20.747,41.465,20.738,41.437,20.707,41.434z M28.803,41.773c-0.019,0-0.051-0.002-0.076-0.011
|
||||||
|
c-0.043-0.015-0.072-0.032-0.106-0.075c-0.049-0.061-0.379-0.581-0.459-0.703l0.34-0.465c0.064-0.089,0.104-0.141,0.142-0.148
|
||||||
|
c0.027-0.007,0.053-0.011,0.071-0.011c0.025,0,0.05-0.014,0.051-0.039c-0.002-0.031-0.032-0.036-0.056-0.036
|
||||||
|
c-0.08,0-0.166,0.005-0.205,0.005c-0.04,0-0.138-0.005-0.22-0.005c-0.029,0-0.06,0.005-0.062,0.036
|
||||||
|
c0.001,0.028,0.025,0.039,0.043,0.039c0.017,0,0.044,0,0.062,0.006c0.019,0.004,0.024,0.015,0.024,0.018
|
||||||
|
c0,0.013-0.006,0.039-0.022,0.064c-0.032,0.053-0.195,0.293-0.268,0.399c-0.079-0.132-0.161-0.266-0.248-0.418
|
||||||
|
c-0.011-0.02-0.021-0.047-0.021-0.052c0-0.002,0.002-0.009,0.017-0.013s0.036-0.006,0.046-0.006c0.021,0,0.049-0.01,0.05-0.039
|
||||||
|
c-0.002-0.031-0.033-0.036-0.06-0.036c-0.079,0-0.208,0.005-0.236,0.005c-0.101,0-0.269-0.005-0.319-0.005
|
||||||
|
c-0.024,0.001-0.055,0.001-0.06,0.034c0,0.021,0.016,0.041,0.038,0.041c0.019,0,0.051,0.004,0.083,0.014
|
||||||
|
c0.068,0.022,0.106,0.062,0.16,0.142l0.361,0.564l-0.401,0.544c-0.072,0.099-0.102,0.125-0.158,0.142
|
||||||
|
c-0.028,0.007-0.06,0.009-0.072,0.009c-0.024,0.001-0.045,0.018-0.045,0.041s0.024,0.036,0.048,0.036h0.036
|
||||||
|
c0.034,0,0.137-0.008,0.176-0.008c0.052,0,0.189,0.008,0.203,0.008h0.038c0.027,0,0.058-0.004,0.06-0.036
|
||||||
|
c-0.002-0.025-0.021-0.039-0.043-0.041c-0.015,0-0.03-0.002-0.045-0.002c-0.013,0-0.022-0.01-0.022-0.018c0-0.001,0-0.003,0-0.003
|
||||||
|
c0-0.015,0.01-0.042,0.028-0.07l0.293-0.453c0.093,0.149,0.201,0.332,0.316,0.52c0.004,0.007,0.005,0.012,0.005,0.014
|
||||||
|
c0,0.005-0.002,0.005-0.002,0.005c-0.021,0.005-0.041,0.021-0.041,0.043c0.006,0.036,0.038,0.034,0.09,0.038
|
||||||
|
c0.172,0.005,0.339,0.005,0.39,0.005h0.062c0.024,0,0.055-0.008,0.059-0.038C28.846,41.788,28.824,41.773,28.803,41.773z
|
||||||
|
M30.581,41.773c-0.018,0-0.05-0.002-0.076-0.011c-0.043-0.015-0.071-0.032-0.105-0.075c-0.049-0.061-0.379-0.581-0.46-0.703
|
||||||
|
l0.341-0.465c0.064-0.089,0.104-0.141,0.141-0.148c0.028-0.007,0.054-0.011,0.072-0.011c0.025,0,0.05-0.014,0.05-0.039
|
||||||
|
c-0.001-0.031-0.031-0.036-0.055-0.036c-0.079,0-0.165,0.005-0.205,0.005s-0.138-0.005-0.219-0.005
|
||||||
|
c-0.029,0-0.062,0.005-0.062,0.036c0.001,0.028,0.026,0.039,0.043,0.039c0.018,0,0.044,0,0.062,0.006
|
||||||
|
c0.019,0.004,0.024,0.015,0.023,0.018c0,0.013-0.006,0.039-0.021,0.064c-0.032,0.053-0.195,0.293-0.269,0.399
|
||||||
|
c-0.078-0.133-0.16-0.266-0.247-0.418c-0.012-0.02-0.021-0.047-0.02-0.052c0-0.002,0.001-0.008,0.016-0.013
|
||||||
|
c0.016-0.004,0.036-0.006,0.047-0.006c0.021,0,0.049-0.01,0.05-0.039c-0.002-0.031-0.033-0.036-0.061-0.036
|
||||||
|
c-0.079,0-0.208,0.005-0.234,0.005c-0.101,0-0.27-0.005-0.319-0.005c-0.025,0.001-0.055,0.001-0.06,0.034
|
||||||
|
c0,0.021,0.017,0.041,0.037,0.041s0.053,0.004,0.083,0.014c0.069,0.022,0.107,0.062,0.16,0.142l0.362,0.564l-0.402,0.544
|
||||||
|
c-0.071,0.099-0.101,0.125-0.157,0.142c-0.029,0.007-0.06,0.009-0.071,0.009c-0.025,0.001-0.045,0.018-0.046,0.041
|
||||||
|
c0,0.023,0.023,0.036,0.048,0.036h0.036c0.035,0,0.137-0.008,0.176-0.008c0.052,0,0.189,0.008,0.203,0.008h0.038
|
||||||
|
c0.026,0,0.058-0.004,0.06-0.036c-0.002-0.025-0.021-0.039-0.043-0.041c-0.015,0-0.031-0.002-0.045-0.002
|
||||||
|
c-0.013,0-0.022-0.008-0.023-0.018c0-0.001,0-0.003,0-0.003c0-0.015,0.01-0.042,0.028-0.07l0.292-0.453
|
||||||
|
c0.092,0.149,0.201,0.332,0.317,0.52c0.004,0.007,0.005,0.012,0.005,0.014c0,0.004-0.002,0.005-0.002,0.005
|
||||||
|
c-0.021,0.005-0.041,0.021-0.041,0.043c0.006,0.036,0.038,0.034,0.09,0.038c0.171,0.005,0.338,0.005,0.389,0.005h0.062
|
||||||
|
c0.024,0,0.055-0.008,0.058-0.038C30.624,41.788,30.603,41.773,30.581,41.773z M32.359,41.773c-0.019,0-0.05-0.002-0.076-0.011
|
||||||
|
c-0.043-0.015-0.072-0.032-0.106-0.075c-0.049-0.061-0.379-0.581-0.459-0.703l0.341-0.465c0.063-0.089,0.104-0.141,0.141-0.148
|
||||||
|
c0.028-0.007,0.053-0.011,0.072-0.011c0.024,0,0.049-0.014,0.05-0.039c-0.002-0.031-0.032-0.036-0.055-0.036
|
||||||
|
c-0.079,0-0.166,0.005-0.205,0.005c-0.04,0-0.139-0.005-0.22-0.005c-0.028,0-0.061,0.005-0.062,0.036
|
||||||
|
c0.001,0.028,0.025,0.039,0.043,0.039c0.016,0,0.044,0,0.061,0.006c0.02,0.004,0.025,0.015,0.024,0.018
|
||||||
|
c0,0.013-0.006,0.039-0.021,0.064c-0.032,0.053-0.195,0.293-0.269,0.399c-0.079-0.132-0.161-0.266-0.248-0.418
|
||||||
|
c-0.011-0.02-0.02-0.047-0.02-0.052c0-0.002,0.002-0.009,0.016-0.013c0.015-0.004,0.036-0.006,0.046-0.006
|
||||||
|
c0.021,0,0.049-0.01,0.05-0.039c-0.002-0.031-0.032-0.036-0.06-0.036c-0.079,0-0.208,0.005-0.236,0.005
|
||||||
|
c-0.099,0-0.268-0.005-0.317-0.005c-0.026,0.001-0.057,0.001-0.061,0.034c0,0.021,0.018,0.041,0.038,0.041
|
||||||
|
c0.02,0,0.052,0.004,0.083,0.014c0.069,0.022,0.107,0.062,0.159,0.142l0.363,0.564l-0.402,0.544
|
||||||
|
c-0.072,0.099-0.101,0.125-0.157,0.142c-0.029,0.007-0.059,0.009-0.072,0.009c-0.023,0.001-0.045,0.018-0.045,0.041
|
||||||
|
s0.024,0.036,0.047,0.036h0.036c0.035,0,0.138-0.008,0.177-0.008c0.052,0,0.188,0.008,0.201,0.008h0.038
|
||||||
|
c0.027,0,0.059-0.004,0.061-0.036c-0.002-0.025-0.021-0.039-0.043-0.041c-0.017,0-0.032-0.002-0.045-0.002
|
||||||
|
c-0.014,0-0.023-0.008-0.024-0.018v-0.003c0-0.015,0.01-0.042,0.028-0.07l0.293-0.453c0.092,0.149,0.201,0.332,0.315,0.52
|
||||||
|
c0.004,0.007,0.006,0.012,0.006,0.014c0,0.004-0.003,0.005-0.003,0.005c-0.021,0.005-0.041,0.021-0.041,0.043
|
||||||
|
c0.007,0.036,0.038,0.033,0.091,0.038c0.172,0.005,0.339,0.005,0.389,0.005h0.062c0.025,0,0.056-0.008,0.059-0.038
|
||||||
|
C32.401,41.788,32.38,41.773,32.359,41.773z M33.453,41.773c-0.03,0-0.078-0.002-0.104-0.007c-0.057-0.012-0.061-0.026-0.069-0.08
|
||||||
|
c-0.009-0.084-0.009-0.246-0.009-0.44v-0.356c0-0.309,0-0.364,0.004-0.429c0.007-0.069,0.015-0.083,0.063-0.094
|
||||||
|
c0.024-0.005,0.039-0.007,0.06-0.007s0.051-0.012,0.051-0.041c-0.005-0.033-0.035-0.033-0.061-0.034
|
||||||
|
c-0.081,0-0.218,0.005-0.27,0.005c-0.059,0-0.202-0.005-0.281-0.005c-0.031,0.001-0.062,0-0.066,0.034
|
||||||
|
c0,0.029,0.028,0.041,0.051,0.041c0.025,0,0.05,0.002,0.071,0.009c0.04,0.015,0.053,0.025,0.06,0.092
|
||||||
|
c0.002,0.063,0.002,0.12,0.002,0.429v0.356c0,0.194,0,0.356-0.01,0.438c-0.009,0.06-0.015,0.073-0.049,0.083
|
||||||
|
c-0.018,0.004-0.04,0.006-0.07,0.006c-0.031,0-0.051,0.02-0.051,0.039c0.001,0.029,0.031,0.038,0.058,0.038
|
||||||
|
c0.084,0,0.228-0.008,0.273-0.008c0.056,0,0.2,0.008,0.338,0.008c0.025,0,0.055-0.008,0.058-0.038
|
||||||
|
C33.504,41.791,33.482,41.773,33.453,41.773z M34.724,41.773c-0.03,0-0.078-0.002-0.104-0.007c-0.056-0.012-0.06-0.026-0.069-0.08
|
||||||
|
c-0.01-0.084-0.01-0.246-0.01-0.44v-0.356c0-0.309,0-0.364,0.005-0.429c0.008-0.069,0.015-0.083,0.064-0.094
|
||||||
|
c0.023-0.005,0.039-0.007,0.059-0.007c0.021,0,0.051-0.012,0.051-0.041c-0.006-0.033-0.034-0.033-0.061-0.034
|
||||||
|
c-0.082,0-0.218,0.005-0.27,0.005c-0.06,0-0.202-0.005-0.281-0.005c-0.032,0.001-0.062,0-0.067,0.034
|
||||||
|
c0,0.029,0.029,0.041,0.05,0.041c0.025,0,0.051,0.002,0.071,0.009c0.04,0.015,0.052,0.025,0.06,0.092
|
||||||
|
c0.003,0.063,0.003,0.12,0.003,0.429v0.356c0,0.194,0,0.356-0.011,0.438c-0.009,0.06-0.015,0.073-0.049,0.083
|
||||||
|
c-0.018,0.004-0.04,0.006-0.069,0.006c-0.031,0-0.051,0.02-0.051,0.039c0.001,0.029,0.03,0.038,0.058,0.038
|
||||||
|
c0.084,0,0.227-0.008,0.273-0.008c0.057,0,0.2,0.008,0.338,0.008c0.025,0,0.056-0.008,0.059-0.038
|
||||||
|
C34.774,41.791,34.753,41.773,34.724,41.773z M35.995,41.773c-0.03,0-0.077-0.002-0.104-0.007c-0.057-0.012-0.061-0.026-0.069-0.08
|
||||||
|
c-0.01-0.084-0.01-0.246-0.01-0.44v-0.356c0-0.309,0-0.364,0.005-0.429c0.007-0.069,0.014-0.083,0.063-0.094
|
||||||
|
c0.024-0.005,0.039-0.007,0.06-0.007s0.05-0.012,0.05-0.041c-0.005-0.033-0.035-0.033-0.06-0.034c-0.081,0-0.218,0.005-0.27,0.005
|
||||||
|
c-0.059,0-0.202-0.005-0.281-0.005c-0.031,0.001-0.062,0-0.066,0.034c0,0.029,0.028,0.041,0.05,0.041
|
||||||
|
c0.025,0,0.05,0.002,0.071,0.009c0.041,0.015,0.053,0.025,0.06,0.092c0.002,0.063,0.002,0.12,0.002,0.429v0.356
|
||||||
|
c0,0.194,0,0.356-0.009,0.438c-0.009,0.06-0.015,0.073-0.049,0.083c-0.019,0.004-0.04,0.006-0.07,0.006
|
||||||
|
c-0.031,0-0.05,0.02-0.05,0.039c0.001,0.029,0.031,0.038,0.058,0.038c0.084,0,0.228-0.008,0.274-0.008
|
||||||
|
c0.056,0,0.2,0.008,0.338,0.008c0.024,0,0.055-0.008,0.057-0.038C36.045,41.79,36.024,41.773,35.995,41.773z M23.406,31.974v-1.188
|
||||||
|
c0-0.222-0.18-0.402-0.402-0.402c-0.222,0-0.402,0.182-0.402,0.402v1.188H23.406z M25.583,31.974v-1.188
|
||||||
|
c0-0.222-0.18-0.402-0.401-0.402c-0.224,0-0.403,0.182-0.403,0.402v1.188H25.583z M24.092,8.076c-0.232,0-0.422,0.189-0.422,0.422
|
||||||
|
c0,0.233,0.189,0.422,0.422,0.422c0.233,0,0.423-0.188,0.423-0.422C24.514,8.265,24.325,8.076,24.092,8.076z M23.879,19.853h0.426
|
||||||
|
v-0.915h-0.426V19.853L23.879,19.853z M19.84,42.941c-0.232,0-0.422,0.188-0.422,0.422s0.189,0.422,0.422,0.422
|
||||||
|
c0.233,0,0.423-0.188,0.423-0.422S20.073,42.941,19.84,42.941z M19.584,19.869v-1.345c-0.003-0.315-0.158-0.533-0.39-0.635
|
||||||
|
c-0.231,0.102-0.386,0.319-0.389,0.636v1.344H19.584z M19.584,15.548V14.68c-0.003-0.315-0.158-0.534-0.39-0.637
|
||||||
|
c-0.231,0.104-0.386,0.321-0.389,0.637v0.867H19.584z M19.402,24.539v-1.127c0-0.116-0.093-0.209-0.209-0.209
|
||||||
|
s-0.209,0.093-0.209,0.209v1.127H19.402z M19.402,27.931v-1.126c0-0.116-0.093-0.211-0.209-0.211s-0.209,0.095-0.209,0.211v1.126
|
||||||
|
H19.402z M19.525,31.721v-1c0-0.188-0.151-0.339-0.338-0.339c-0.188,0-0.338,0.151-0.338,0.339v1H19.525z M19.194,3.409
|
||||||
|
c-0.233,0-0.422,0.189-0.422,0.422c0,0.233,0.188,0.422,0.422,0.422c0.232,0,0.422-0.188,0.422-0.422
|
||||||
|
C19.616,3.598,19.427,3.409,19.194,3.409z M29.375,19.869v-1.345c-0.002-0.315-0.158-0.533-0.389-0.635
|
||||||
|
c-0.231,0.102-0.387,0.319-0.389,0.636v1.344H29.375z M29.375,15.548V14.68c-0.002-0.315-0.158-0.534-0.389-0.637
|
||||||
|
c-0.231,0.104-0.387,0.321-0.389,0.637v0.867H29.375z M29.194,24.539v-1.127c0-0.116-0.094-0.209-0.209-0.209
|
||||||
|
s-0.209,0.093-0.209,0.209v1.127H29.194z M29.194,27.931v-1.126c0-0.116-0.094-0.211-0.209-0.211s-0.209,0.095-0.209,0.211v1.126
|
||||||
|
H29.194z M29.317,31.721v-1c0-0.188-0.151-0.339-0.338-0.339c-0.188,0-0.338,0.151-0.338,0.339v1H29.317z M28.985,3.409
|
||||||
|
c-0.233,0-0.422,0.189-0.422,0.422c0,0.233,0.188,0.422,0.422,0.422c0.232,0,0.422-0.188,0.422-0.422
|
||||||
|
C29.408,3.598,29.219,3.409,28.985,3.409z M60.109,21.36c-3.838,0-4.703-2.09-4.703-4.415V8.998h2.342v7.803
|
||||||
|
c0,1.532,0.505,2.613,2.523,2.613c1.802,0,2.559-0.757,2.559-2.829V8.998h2.325v7.442C65.155,19.774,63.316,21.36,60.109,21.36z
|
||||||
|
M73.155,21.162v-5.729c0-0.938-0.253-1.496-1.1-1.496c-1.172,0-2.091,1.334-2.091,2.901v4.325h-2.307v-8.956h2.181
|
||||||
|
c0,0.415-0.035,1.117-0.126,1.586l0.019,0.019c0.541-1.063,1.586-1.803,3.046-1.803c2.018,0,2.666,1.298,2.666,2.865v6.289
|
||||||
|
L73.155,21.162L73.155,21.162z M79.102,11.052c-0.793,0-1.424-0.631-1.424-1.405c0-0.757,0.631-1.389,1.424-1.389
|
||||||
|
s1.44,0.613,1.44,1.389C80.543,10.422,79.895,11.052,79.102,11.052z M77.948,21.162v-8.956h2.307v8.956H77.948z M87.211,21.162
|
||||||
|
h-2.343l-3.314-8.956h2.521l1.424,4.036c0.216,0.613,0.434,1.334,0.596,1.982h0.035c0.145-0.612,0.324-1.298,0.54-1.91l1.441-4.108
|
||||||
|
h2.451L87.211,21.162z M98.797,17.054h-5.551c-0.018,1.676,0.812,2.485,2.469,2.485c0.884,0,1.839-0.198,2.613-0.559l0.216,1.784
|
||||||
|
c-0.955,0.378-2.09,0.576-3.207,0.576c-2.848,0-4.433-1.423-4.433-4.576c0-2.739,1.514-4.758,4.198-4.758
|
||||||
|
c2.611,0,3.767,1.784,3.767,4C98.869,16.314,98.851,16.675,98.797,17.054z M95.03,13.702c-0.955,0-1.622,0.703-1.748,1.784h3.298
|
||||||
|
C96.616,14.368,96.004,13.702,95.03,13.702z M105.59,14.278c-1.657-0.343-2.485,0.739-2.485,3.226v3.658h-2.308v-8.956h2.181
|
||||||
|
c0,0.45-0.055,1.171-0.162,1.802h0.036c0.433-1.135,1.298-2.126,2.848-2L105.59,14.278z M108.743,21.342
|
||||||
|
c-0.647,0-1.298-0.071-1.874-0.161l0.055-1.893c0.559,0.145,1.242,0.271,1.929,0.271c0.883,0,1.459-0.361,1.459-0.956
|
||||||
|
c0-1.585-3.621-0.686-3.621-3.73c0-1.567,1.278-2.865,3.802-2.865c0.522,0,1.1,0.072,1.64,0.162l-0.07,1.82
|
||||||
|
c-0.505-0.146-1.101-0.234-1.658-0.234c-0.901,0-1.333,0.36-1.333,0.919c0,1.46,3.676,0.812,3.676,3.713
|
||||||
|
C112.744,20.153,111.194,21.342,108.743,21.342z M115.861,11.052c-0.793,0-1.424-0.631-1.424-1.405c0-0.757,0.631-1.389,1.424-1.389
|
||||||
|
s1.44,0.613,1.44,1.389C117.302,10.422,116.654,11.052,115.861,11.052z M114.708,21.162v-8.956h2.308v8.956H114.708z
|
||||||
|
M122.851,21.342c-1.982,0-2.613-0.721-2.613-2.811V13.99h-1.531v-1.784h1.531V9.449l2.307-0.613v3.37h2.182v1.784h-2.182v3.928
|
||||||
|
c0,1.153,0.271,1.479,1.063,1.479c0.378,0,0.793-0.055,1.117-0.145v1.856C124.147,21.252,123.481,21.342,122.851,21.342z
|
||||||
|
M131.731,21.162c0-0.521,0.019-1.045,0.091-1.514l-0.019-0.019c-0.434,1.01-1.531,1.712-2.865,1.712c-1.621,0-2.56-0.919-2.56-2.36
|
||||||
|
c0-2.145,2.126-3.279,5.172-3.279v-0.487c0-0.937-0.45-1.423-1.747-1.423c-0.812,0-1.894,0.271-2.649,0.703l-0.198-1.928
|
||||||
|
c0.901-0.324,2.056-0.56,3.208-0.56c2.884,0,3.694,1.172,3.694,3.118v3.73c0,0.721,0.018,1.567,0.054,2.307L131.731,21.162
|
||||||
|
L131.731,21.162z M128.561,10.729c-0.687,0-1.243-0.56-1.243-1.244c0-0.703,0.558-1.244,1.243-1.244
|
||||||
|
c0.685,0,1.243,0.541,1.243,1.244C129.804,10.169,129.245,10.729,128.561,10.729z M131.552,17.198c-2.433,0-2.974,0.703-2.974,1.423
|
||||||
|
c0,0.577,0.396,0.955,1.063,0.955c1.135,0,1.909-1.081,1.909-2.162L131.552,17.198L131.552,17.198z M132.2,10.729
|
||||||
|
c-0.685,0-1.243-0.56-1.243-1.244c0-0.703,0.56-1.244,1.243-1.244s1.244,0.541,1.244,1.244
|
||||||
|
C133.444,10.169,132.885,10.729,132.2,10.729z M139.55,21.342c-1.981,0-2.612-0.721-2.612-2.811V13.99h-1.531v-1.784h1.531V9.449
|
||||||
|
l2.307-0.613v3.37h2.181v1.784h-2.181v3.928c0,1.153,0.271,1.479,1.063,1.479c0.379,0,0.793-0.055,1.116-0.145v1.856
|
||||||
|
C140.847,21.252,140.181,21.342,139.55,21.342z M54.955,40.202V38.4l4.379-7.137c0.252-0.414,0.504-0.774,0.793-1.153
|
||||||
|
c-0.433,0.019-1.009,0.036-2.217,0.036h-2.811v-2.108h7.856v1.874l-4.631,7.316c-0.18,0.288-0.342,0.559-0.558,0.848
|
||||||
|
c0.306-0.036,1.135-0.036,2.631-0.036h2.631v2.162H54.955z M70.484,40.202c0-0.414,0.018-1.117,0.107-1.586l-0.018-0.018
|
||||||
|
c-0.541,1.062-1.567,1.802-3.045,1.802c-2.02,0-2.667-1.298-2.667-2.865v-6.289h2.289v5.73c0,0.937,0.252,1.496,1.117,1.496
|
||||||
|
c1.171,0,2.071-1.334,2.071-2.901v-4.325h2.308v8.956H70.484z M66.826,29.769c-0.685,0-1.243-0.56-1.243-1.244
|
||||||
|
c0-0.702,0.56-1.243,1.243-1.243c0.685,0,1.244,0.541,1.244,1.243C68.069,29.21,67.511,29.769,66.826,29.769z M70.466,29.769
|
||||||
|
c-0.685,0-1.243-0.56-1.243-1.244c0-0.702,0.559-1.243,1.243-1.243c0.686,0,1.243,0.541,1.243,1.243
|
||||||
|
C71.709,29.21,71.151,29.769,70.466,29.769z M79.926,33.318c-1.656-0.343-2.485,0.739-2.485,3.226v3.658h-2.308v-8.956h2.182
|
||||||
|
c0,0.45-0.055,1.171-0.162,1.802h0.036c0.432-1.135,1.297-2.126,2.847-2L79.926,33.318z M82.854,30.093
|
||||||
|
c-0.793,0-1.424-0.631-1.424-1.405c0-0.756,0.631-1.388,1.424-1.388s1.44,0.612,1.44,1.388
|
||||||
|
C84.295,29.462,83.646,30.093,82.854,30.093z M81.7,40.202v-8.956h2.307v8.956H81.7z M89.729,40.364
|
||||||
|
c-2.486,0-4.036-1.297-4.036-4.343c0-2.793,1.46-4.938,4.632-4.938c0.612,0,1.261,0.09,1.838,0.252l-0.233,2.001
|
||||||
|
c-0.486-0.181-1.046-0.325-1.622-0.325c-1.46,0-2.198,1.081-2.198,2.775c0,1.531,0.595,2.577,2.126,2.577
|
||||||
|
c0.612,0,1.279-0.127,1.767-0.379l0.181,1.965C91.567,40.185,90.684,40.364,89.729,40.364z M99.478,40.202v-5.729
|
||||||
|
c0-0.938-0.252-1.496-1.1-1.496c-1.172,0-2.091,1.334-2.091,2.9v4.325h-2.307V27.047h2.307v3.839c0,0.541-0.036,1.298-0.162,1.82
|
||||||
|
l0.036,0.019c0.522-1.01,1.55-1.677,2.938-1.677c2.018,0,2.667,1.299,2.667,2.865v6.289H99.478z M105.334,31.716
|
||||||
|
c-1.461,0-1.813-0.788-1.813-1.706v-2.972h1.061v2.91c0,0.537,0.17,0.877,0.822,0.877c0.599,0,0.83-0.252,0.83-0.938v-2.85h1.047
|
||||||
|
v2.788C107.279,31.118,106.552,31.716,105.334,31.716z M108.163,31.628v-0.83l1.477-2.468c0.073-0.122,0.155-0.238,0.243-0.354
|
||||||
|
c-0.12,0.007-0.277,0.014-0.721,0.014h-0.951v-0.952h2.972v0.863l-1.558,2.502c-0.055,0.089-0.107,0.164-0.177,0.259
|
||||||
|
c0.089-0.014,0.347-0.014,0.891-0.014h0.87v0.979L108.163,31.628L108.163,31.628z M114.922,31.628v-1.911h-1.68v1.911h-1.061v-4.59
|
||||||
|
h1.061V28.8h1.68v-1.762h1.062v4.59H114.922z M0,24.091C0,10.786,10.786,0,24.091,0l0,0c13.306,0,24.092,10.786,24.092,24.091l0,0
|
||||||
|
c0,13.306-10.786,24.092-24.092,24.092l0,0C10.786,48.182,0,37.396,0,24.091L0,24.091z M7.477,7.477
|
||||||
|
C3.225,11.73,0.595,17.602,0.595,24.091l0,0c0,6.489,2.629,12.361,6.882,16.614l0,0c4.253,4.252,10.125,6.881,16.613,6.881l0,0
|
||||||
|
c6.489,0,12.361-2.629,16.614-6.881l0,0c4.252-4.253,6.882-10.125,6.882-16.614l0,0c0-6.488-2.629-12.36-6.882-16.613l0,0
|
||||||
|
C36.452,3.224,30.58,0.594,24.091,0.594l0,0C17.602,0.594,11.73,3.224,7.477,7.477L7.477,7.477z M24.456,39.232h0.194v-0.34h-0.462
|
||||||
|
v0.753h0.269L24.456,39.232z M24.091,1.568c-12.436,0-22.516,10.081-22.516,22.516c0,12.437,10.081,22.518,22.516,22.518
|
||||||
|
c12.436,0,22.516-10.081,22.517-22.518C46.606,11.649,36.526,1.568,24.091,1.568z M21.598,43.19h-1.021v0.34h1.021v2.53
|
||||||
|
c-5.112-0.573-9.696-2.889-13.146-6.338c-0.495-0.495-0.967-1.015-1.414-1.555c0.091-0.549,0.565-0.967,1.141-0.967
|
||||||
|
c0.639,0,1.155,0.518,1.155,1.156v1.11h1.468v-0.007l0,0v-1.104c0-0.64,0.519-1.156,1.156-1.156c0.639,0,1.157,0.518,1.157,1.156
|
||||||
|
v1.11h1.468v-0.007l0,0v-1.104c0-0.64,0.519-1.156,1.156-1.156s1.157,0.518,1.157,1.156v1.11h1.468v-0.007l0,0v-1.104
|
||||||
|
c0-0.64,0.518-1.156,1.157-1.156c0.639,0,1.155,0.518,1.155,1.156v1.11h0.922V43.19z M25.794,46.135
|
||||||
|
c-0.562,0.044-1.13,0.065-1.703,0.065c-0.571,0-1.14-0.021-1.7-0.063l-0.418-2.606h0.481c-0.12-0.286-0.329-0.786-0.409-0.989
|
||||||
|
c-0.13-0.326-0.032-0.542,0.185-0.626c0.211-0.082,0.449,0.034,0.523,0.312l0.211,0.792h0.309v0.511l0,0v0.102h-0.729v0.341h1.066
|
||||||
|
l0.001-0.441h1.293l-0.001-0.511h0.31l0.213-0.792c0.075-0.278,0.315-0.396,0.528-0.312c0.218,0.084,0.315,0.3,0.186,0.626
|
||||||
|
c-0.035,0.089-0.093,0.235-0.154,0.396l-0.288,0.695H24.92v0.341h1.005l0.184-0.441h0.104L25.794,46.135z M23.391,38.684h1.4
|
||||||
|
c0.055,0.129,0.188,0.454,0.271,0.643c0.057,0.131,0.13,0.271,0.041,0.416c-0.106,0.171-0.245,0.146-0.342,0.139
|
||||||
|
c-0.006-0.001-0.012,0-0.018-0.001c-0.098,0.269-0.352,0.459-0.653,0.459c-0.301,0-0.555-0.19-0.652-0.459
|
||||||
|
c-0.005,0.001-0.013,0-0.018,0.001c-0.098,0.009-0.235,0.032-0.342-0.139c-0.089-0.145-0.016-0.285,0.041-0.416
|
||||||
|
C23.204,39.138,23.337,38.812,23.391,38.684z M23.18,37.23l0.447,0.641l0.458-0.869l0.458,0.869l0.448-0.641l-0.178,1.111h-1.456
|
||||||
|
L23.18,37.23z M23.715,40.376c0.105,0.293,0.385,0.504,0.715,0.504c0.356,0,0.654-0.247,0.736-0.58
|
||||||
|
c0.222,0.012,0.442,0.075,0.661,0.261c0.211,0.177,0.293,0.48,0.323,0.632h-4.116c0.029-0.15,0.112-0.455,0.323-0.632
|
||||||
|
C22.808,40.181,23.268,40.305,23.715,40.376z M25.414,41.532c-0.155,0.586-0.688,1.019-1.322,1.019
|
||||||
|
c-0.634,0-1.167-0.433-1.321-1.019H25.414z M39.729,39.723c-3.45,3.449-8.033,5.764-13.145,6.337v-2.53h0.935
|
||||||
|
c1.731,0,2.983-0.146,2.983-0.146s-1.289-0.194-2.983-0.194h-0.935v-3.721h1.117v-0.007l0,0v-1.104c0-0.64,0.519-1.156,1.157-1.156
|
||||||
|
s1.157,0.519,1.157,1.156v1.11h1.467v-0.007l0,0v-1.104c0-0.64,0.518-1.156,1.156-1.156c0.638,0,1.155,0.519,1.155,1.156v1.11h1.469
|
||||||
|
v-0.007l0,0v-1.104c0-0.64,0.518-1.156,1.156-1.156c0.64,0,1.156,0.519,1.156,1.156v1.11h1.468v-0.007l0,0v-1.104
|
||||||
|
c0-0.64,0.519-1.156,1.157-1.156c0.506,0,0.937,0.326,1.092,0.78C40.803,38.589,40.281,39.171,39.729,39.723z M41.541,37.673
|
||||||
|
c-0.257-0.541-0.808-0.916-1.445-0.916c-0.885,0-1.602,0.718-1.602,1.603v0.517h-0.579v-0.517c0-0.885-0.717-1.603-1.601-1.603
|
||||||
|
s-1.602,0.718-1.602,1.603v0.517h-0.58v-0.517c0-0.885-0.717-1.603-1.601-1.603s-1.601,0.718-1.601,1.603v0.517h-0.58v-0.517
|
||||||
|
c0-0.885-0.718-1.603-1.602-1.603s-1.601,0.718-1.601,1.603v0.517h-0.598c-0.182-1.198-1.213-2.118-2.462-2.118
|
||||||
|
s-2.279,0.92-2.462,2.118h-0.614v-0.517c0-0.885-0.718-1.603-1.602-1.603s-1.601,0.718-1.601,1.603v0.517h-0.58v-0.517
|
||||||
|
c0-0.885-0.716-1.603-1.602-1.603c-0.884,0-1.601,0.718-1.601,1.603v0.517h-0.58v-0.517c0-0.885-0.717-1.603-1.601-1.603
|
||||||
|
c-0.885,0-1.602,0.718-1.602,1.603v0.517H9.672v-0.517c0-0.885-0.717-1.603-1.601-1.603c-0.634,0-1.181,0.369-1.44,0.903
|
||||||
|
c-0.39-0.501-0.758-1.02-1.104-1.554h37.129C42.306,36.645,41.935,37.168,41.541,37.673z M43.027,35.515H5.155
|
||||||
|
c-0.11-0.183-0.217-0.366-0.322-0.553h38.517C43.243,35.148,43.137,35.333,43.027,35.515z M20.275,7.089v0.187
|
||||||
|
C20.27,7.313,20.26,7.368,20.241,7.42c-0.032,0.085-0.111,0.221-0.217,0.386c-0.04,0.064-0.11,0.174-0.188,0.33
|
||||||
|
C19.735,7.982,19.65,7.88,19.59,7.817c-0.066-0.07-0.154-0.16-0.228-0.246c-0.157-0.187-0.166-0.298-0.166-0.298l0,0
|
||||||
|
c0,0-0.005,0.11-0.163,0.297c-0.073,0.085-0.161,0.175-0.228,0.246c-0.062,0.063-0.147,0.167-0.25,0.325
|
||||||
|
c-0.08-0.161-0.151-0.272-0.192-0.341c-0.105-0.165-0.185-0.3-0.217-0.385c-0.03-0.081-0.041-0.174-0.042-0.184V7.089H20.275z
|
||||||
|
M18.109,6.749c0.017-0.277,0.092-0.805,0.362-1.164c0.204-0.278,0.407-0.443,0.557-0.54C19.096,5,19.151,4.973,19.19,4.954
|
||||||
|
c0.04,0.02,0.095,0.046,0.164,0.091c0.147,0.097,0.352,0.262,0.557,0.54c0.271,0.359,0.345,0.887,0.362,1.164H18.109z M20.354,7.988
|
||||||
|
c0.062,0.085,0.125,0.209,0.194,0.359c0.096,0.233,0.121,0.462,0.124,0.63v2.492h-0.642c0-0.548,0-1.756,0-2.491
|
||||||
|
c0.002-0.171,0.033-0.409,0.124-0.632C20.234,8.17,20.296,8.07,20.354,7.988z M19.682,8.994l-0.001,2.476h-0.974
|
||||||
|
c0-0.689-0.001-2.477-0.001-2.478c0.008-0.162,0.021-0.386,0.138-0.617c0.122-0.242,0.354-0.386,0.354-0.386h0.001
|
||||||
|
c0,0,0.219,0.144,0.342,0.386C19.664,8.618,19.675,8.828,19.682,8.994z M18.245,8.343c0.091,0.224,0.106,0.465,0.109,0.636
|
||||||
|
l0.003,2.491h-0.634V8.975c0.003-0.168,0.021-0.396,0.116-0.631c0.07-0.149,0.138-0.273,0.195-0.358
|
||||||
|
C18.106,8.091,18.175,8.193,18.245,8.343z M21.164,11.877v0.807h-3.941v-0.807H21.164z M21.164,13.022v3.141H20.21V14.75
|
||||||
|
c0.001-0.469-0.173-0.866-0.459-1.138c-0.153-0.147-0.338-0.258-0.542-0.328c-0.013-0.004-0.012-0.005-0.012-0.005
|
||||||
|
s-0.002,0-0.015,0.005c-0.204,0.07-0.39,0.181-0.543,0.328c-0.286,0.271-0.459,0.669-0.458,1.138v1.414h-0.958v-3.141L21.164,13.022
|
||||||
|
L21.164,13.022z M19.87,14.75v1.414h-1.35V14.75c0.005-0.546,0.272-0.925,0.675-1.103C19.596,13.824,19.865,14.203,19.87,14.75z
|
||||||
|
M21.164,16.503v3.995h-0.876v-1.995c0-0.468-0.176-0.875-0.47-1.155c-0.157-0.15-0.344-0.263-0.55-0.333
|
||||||
|
c-0.011-0.005-0.074-0.024-0.074-0.024s-0.062,0.02-0.067,0.022c-0.207,0.07-0.396,0.185-0.555,0.335
|
||||||
|
c-0.294,0.279-0.471,0.688-0.47,1.154v1.995h-0.88v-3.995L21.164,16.503L21.164,16.503z M19.948,18.506v1.992h-1.507v-1.995
|
||||||
|
c0-0.384,0.141-0.694,0.364-0.909c0.11-0.105,0.241-0.185,0.389-0.241h0.001c0.147,0.058,0.276,0.136,0.387,0.24
|
||||||
|
c0.226,0.215,0.364,0.525,0.365,0.91L19.948,18.506z M21.164,20.838v4.755h-3.941v-4.755H21.164z M21.164,25.933v3.103h-3.941
|
||||||
|
v-3.103H21.164z M21.164,29.375v5.247h-3.941v-5.247H21.164z M24.634,16.538v1.416l-0.542-0.542l-0.541,0.541v-1.415H24.634z
|
||||||
|
M23.566,16.13l0.396-1.694l0.163-2.835l0.098,2.833l0.396,1.696H23.566z M24.092,17.988l2.511,2.51h-5.021L24.092,17.988z
|
||||||
|
M26.603,20.838v1.921h-5.037v-1.921H26.603z M26.603,23.354v2.238h-1.575v-0.561c0-0.516-0.419-0.938-0.938-0.938
|
||||||
|
c-0.517,0-0.937,0.421-0.937,0.938v0.561h-1.589v-2.238H26.603z M23.155,25.933v1.998h1.873v-1.998h1.575v3.103h-5.037v-3.103
|
||||||
|
H23.155z M26.603,29.375v5.247h-5.037v-5.247H26.603z M30.067,7.089v0.187c-0.005,0.038-0.017,0.093-0.035,0.145
|
||||||
|
C30,7.505,29.921,7.64,29.815,7.805c-0.04,0.064-0.11,0.174-0.188,0.33c-0.101-0.153-0.186-0.256-0.246-0.318
|
||||||
|
c-0.065-0.07-0.154-0.16-0.228-0.246c-0.158-0.187-0.166-0.298-0.166-0.298l0,0c0,0-0.005,0.11-0.163,0.297
|
||||||
|
c-0.073,0.085-0.161,0.175-0.228,0.246c-0.062,0.063-0.147,0.167-0.25,0.325c-0.08-0.161-0.151-0.272-0.192-0.341
|
||||||
|
c-0.105-0.165-0.185-0.3-0.217-0.385c-0.031-0.082-0.041-0.177-0.042-0.185V7.089H30.067z M27.901,6.749
|
||||||
|
c0.017-0.277,0.092-0.805,0.362-1.164c0.204-0.278,0.407-0.443,0.557-0.54C28.888,5,28.942,4.973,28.982,4.954
|
||||||
|
c0.04,0.02,0.095,0.046,0.163,0.091c0.147,0.097,0.352,0.262,0.557,0.54c0.271,0.359,0.345,0.887,0.362,1.164H27.901z M30.146,7.988
|
||||||
|
c0.061,0.085,0.125,0.209,0.193,0.359c0.096,0.233,0.121,0.462,0.124,0.63v2.492h-0.642c0-0.548,0-1.756,0-2.491
|
||||||
|
c0.003-0.171,0.033-0.409,0.125-0.632C30.025,8.17,30.088,8.07,30.146,7.988z M29.473,8.994l-0.001,2.476H28.5
|
||||||
|
c0-0.689-0.001-2.477-0.001-2.478c0.008-0.162,0.021-0.386,0.138-0.617c0.122-0.242,0.354-0.386,0.354-0.386h0.001
|
||||||
|
c0,0,0.219,0.144,0.342,0.386C29.455,8.618,29.466,8.828,29.473,8.994z M28.036,8.343c0.091,0.224,0.106,0.465,0.109,0.636
|
||||||
|
l0.003,2.491h-0.635V8.975c0.004-0.168,0.021-0.396,0.116-0.631c0.07-0.149,0.138-0.273,0.194-0.358
|
||||||
|
C27.898,8.091,27.966,8.193,28.036,8.343z M30.956,11.877v0.807h-3.941v-0.807H30.956z M30.956,13.022v3.141h-0.954V14.75
|
||||||
|
c0-0.469-0.173-0.866-0.459-1.138c-0.153-0.147-0.339-0.258-0.543-0.328c-0.013-0.004-0.012-0.005-0.012-0.005s-0.002,0-0.015,0.005
|
||||||
|
c-0.204,0.07-0.39,0.181-0.543,0.328c-0.286,0.271-0.459,0.669-0.459,1.138v1.414h-0.958v-3.141L30.956,13.022L30.956,13.022z
|
||||||
|
M29.662,14.75v1.414h-1.351V14.75c0.005-0.546,0.272-0.925,0.675-1.103C29.388,13.824,29.657,14.203,29.662,14.75z M30.956,16.503
|
||||||
|
v3.995H30.08v-1.995c0-0.468-0.176-0.875-0.47-1.155c-0.157-0.15-0.344-0.263-0.55-0.333c-0.01-0.005-0.074-0.024-0.074-0.024
|
||||||
|
s-0.062,0.02-0.068,0.022c-0.207,0.07-0.396,0.185-0.555,0.335c-0.294,0.279-0.47,0.688-0.47,1.154v1.995h-0.88v-3.995
|
||||||
|
L30.956,16.503L30.956,16.503z M29.74,18.506v1.992h-1.506v-1.995c0-0.384,0.141-0.694,0.364-0.909
|
||||||
|
c0.11-0.105,0.241-0.185,0.388-0.241h0.001c0.147,0.058,0.277,0.136,0.388,0.24c0.225,0.215,0.364,0.525,0.365,0.91V18.506z
|
||||||
|
M30.956,20.838v4.755h-3.941v-4.755H30.956z M30.956,25.933v3.103h-3.941v-3.103H30.956z M30.956,29.375v5.247h-3.941v-5.247
|
||||||
|
H30.956z M31.562,34.622V11.469h-0.497V8.917c-0.005-0.317-0.051-0.519-0.191-0.844c-0.036-0.082-0.087-0.172-0.145-0.262V6.889
|
||||||
|
c0,0-0.001-0.725-0.3-1.27c-0.325-0.595-0.956-0.92-1.264-1.049c-0.07-0.033-0.117-0.048-0.126-0.05l-0.051-0.019l-0.063,0.019
|
||||||
|
c-0.03,0.009-0.529,0.167-1.004,0.812c-0.43,0.593-0.444,1.354-0.448,1.563c0,0.034,0.001,0.054,0.001,0.057v0.851
|
||||||
|
c-0.058,0.084-0.138,0.211-0.226,0.398c-0.132,0.324-0.141,0.624-0.146,0.804v2.465h-0.5v8.452l-1.368-1.367v-2.413l-0.464-1.558
|
||||||
|
l-0.68-5.341l0,0l-0.501,5.117l-0.466,1.782v2.234l-1.354,1.355v-8.265h-0.497V8.917c-0.005-0.317-0.051-0.519-0.192-0.844
|
||||||
|
c-0.035-0.082-0.087-0.172-0.144-0.262V6.889c0,0-0.001-0.725-0.3-1.27c-0.326-0.595-0.957-0.92-1.264-1.049
|
||||||
|
c-0.07-0.033-0.117-0.048-0.126-0.05l-0.051-0.019l-0.063,0.019c-0.031,0.009-0.529,0.167-1.005,0.812
|
||||||
|
c-0.43,0.593-0.444,1.354-0.448,1.563c0,0.034,0,0.054,0,0.057v0.851c-0.058,0.084-0.138,0.211-0.226,0.398
|
||||||
|
c-0.131,0.324-0.141,0.624-0.145,0.804v2.465h-0.5v23.151H4.646c-1.702-3.133-2.672-6.721-2.672-10.538
|
||||||
|
c0-6.106,2.475-11.635,6.477-15.639c4.003-4.002,9.531-6.479,15.639-6.479c6.108,0,11.637,2.476,15.64,6.479
|
||||||
|
c4.002,4.003,6.478,9.531,6.478,15.639c0,3.817-0.97,7.405-2.672,10.538L31.562,34.622L31.562,34.622z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 53 KiB |
@ -646,8 +646,8 @@ function handleAssessmentResponse(depict_url, data) {
|
|||||||
var reactivityCentersImgSrc = null;
|
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 %}
|
||||||
|
|||||||
55
templates/compare.html
Normal file
55
templates/compare.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{% extends "framework.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="text" class="form-control" id="smiles" name="smiles" placeholder="SMILES"
|
||||||
|
value="{{ smiles }}"/>
|
||||||
|
<input type="text" class="form-control" id="smiles" name="smirks" placeholder="SMIRKS"
|
||||||
|
value="{{ smirks }}"/>
|
||||||
|
<button type="submit" class="btn btn-primary">Test</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% if result %}
|
||||||
|
{{ smiles }}<p></p>
|
||||||
|
<img width='400' src='{% url 'depict' %}?smiles={{ smiles|urlencode }}'><br>
|
||||||
|
<p></p>
|
||||||
|
{% if rule %}
|
||||||
|
{{ smirks }}
|
||||||
|
<p></p>
|
||||||
|
{{ rule.reactants_smarts }}
|
||||||
|
<p></p>
|
||||||
|
{{ rule.products_smarts }}
|
||||||
|
<p></p>
|
||||||
|
<div>
|
||||||
|
{{ rule.as_svg|safe }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<h2>Diff</h2>
|
||||||
|
{% if diff %}
|
||||||
|
{% for d in diff %}
|
||||||
|
{{ d }}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{{ "No diff" }}
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2>Ambit</h2>
|
||||||
|
{% for p in ambit_res %}
|
||||||
|
{{ p }}<br>
|
||||||
|
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2>RDKit</h2>
|
||||||
|
{% for p in rdkit_res %}
|
||||||
|
{{ p }}<br>
|
||||||
|
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock content %}
|
||||||
@ -6,8 +6,7 @@
|
|||||||
<h4 class="alert-heading">{{ error_message }}</h4>
|
<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>
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
169
templates/modals/objects/generic_set_aliases_modal.html
Normal file
169
templates/modals/objects/generic_set_aliases_modal.html
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.alias-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
cursor: text;
|
||||||
|
min-height: 38px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alias {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #5bc0de;
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin: 3px 3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alias .remove {
|
||||||
|
margin-left: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alias-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
margin: 3px 3px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control.alias-container {
|
||||||
|
height: auto;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="modal fade bs-modal-lg" id="set_aliases_modal" tabindex="-1" aria-labelledby="set_aliases_modal"
|
||||||
|
aria-modal="true" role="dialog">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<h4 class="modal-title">Set Aliases for {{ current_object.name }}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="set_aliases_modal_form" accept-charset="UTF-8" action="{{ current_object.url }}"
|
||||||
|
data-remote="true" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<label for="alias-input">Aliases:</label>
|
||||||
|
<div class="form-control alias-container" id="alias-box">
|
||||||
|
{% for alias in current_object.aliases %}
|
||||||
|
<span class="alias">{{ alias|escape }}<span class="remove">×</span></span>
|
||||||
|
{% endfor %}
|
||||||
|
<input type="text" id="alias-input" class="alias-input" placeholder="Add Alias...">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="add-alias-error-message" class="alert alert-danger" role="alert" style="display: none">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="set_aliases_modal_form_submit">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
function addAlias(aliasText) {
|
||||||
|
aliasText = aliasText.trim();
|
||||||
|
if (aliasText === '') return;
|
||||||
|
|
||||||
|
// Avoid duplicate aliass
|
||||||
|
var exists = false;
|
||||||
|
$('#alias-box .alias').each(function () {
|
||||||
|
if ($(this).text().replace('×', '').trim().toLowerCase() === aliasText.toLowerCase()) {
|
||||||
|
exists = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
var aliasHtml = '<span class="alias">' + $('<div>').text(aliasText).html() +
|
||||||
|
'<span class="remove">×</span></span>';
|
||||||
|
$(aliasHtml).insertBefore('#alias-input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#alias-input').val('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add alias when Enter is pressed
|
||||||
|
$('#alias-input').on('keypress', function (e) {
|
||||||
|
if (e.which === 13) {
|
||||||
|
e.preventDefault();
|
||||||
|
addAlias($(this).val());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add alias when input loses focus
|
||||||
|
$('#alias-input').on('blur', function () {
|
||||||
|
var val = $(this).val();
|
||||||
|
if (val.trim() !== '') {
|
||||||
|
addAlias(val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove alias when clicking ×
|
||||||
|
$('#alias-box').on('click', '.remove', function () {
|
||||||
|
$(this).closest('.alias').remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Focus input when clicking the container
|
||||||
|
$('#alias-box').on('click', function () {
|
||||||
|
$('#alias-input').focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$('#set_aliases_modal_form_submit').on('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let aliases = [];
|
||||||
|
$('#alias-box .alias').each(function () {
|
||||||
|
aliases.push($(this).text().replace('×', '').trim())
|
||||||
|
});
|
||||||
|
|
||||||
|
if (aliases.length === 0) {
|
||||||
|
// Set empty string for deletion of all aliases
|
||||||
|
// If empty list is sent, its gets removed entirely from post data
|
||||||
|
aliases = ['']
|
||||||
|
}
|
||||||
|
|
||||||
|
formData = {
|
||||||
|
'aliases': aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'post',
|
||||||
|
data: formData,
|
||||||
|
url: '{{ current_object.url }}',
|
||||||
|
traditional: true,
|
||||||
|
success: function (data, textStatus) {
|
||||||
|
window.location.href = data.success;
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
$('#add-alias-error-message').append('<p>Setting aliases failed!</p>');
|
||||||
|
$('#add-alias-error-message').show(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!-- Delete Object -->
|
||||||
|
<div id="generic_set_external_reference_modal" class="modal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title">Add External References</h3>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="generic-set-external-reference-modal-form" accept-charset="UTF-8"
|
||||||
|
action="{{ current_object.url }}"
|
||||||
|
data-remote="true" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<label for="database-select">Select the Database you want to attach an External Reference
|
||||||
|
for</label>
|
||||||
|
<select id="database-select" name="selected-database" data-actions-box='true' class="form-control"
|
||||||
|
data-width='100%'>
|
||||||
|
<option disabled selected>Select Database</option>
|
||||||
|
{% for entity, databases in meta.external_databases.items %}
|
||||||
|
{% if entity == object_type %}
|
||||||
|
{% for db in databases %}
|
||||||
|
<option id="db-select-{{ db.database.pk }}" data-input-placeholder="{{ db.placeholder }}"
|
||||||
|
value="{{ db.database.id }}">{{ db.database.name }}</option>`
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<p></p>
|
||||||
|
<div id="input-div" style="display: none">
|
||||||
|
<label for="identifier" >The reference</label>
|
||||||
|
<input type="text" id="identifier" name="identifier" class="form-control" placeholder="">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="generic-set-external-reference-modal-form-submit">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
$("#database-select").on("change", function () {
|
||||||
|
let selected = $(this).val();
|
||||||
|
$("#identifier").attr("placeholder", $('#db-select-' + selected).data('input-placeholder'));
|
||||||
|
$("#input-div").show();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#generic-set-external-reference-modal-form-submit').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#generic-set-external-reference-modal-form').submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@ -18,6 +18,7 @@
|
|||||||
<select id="scenario-select" name="selected-scenarios" data-actions-box='true' class="form-control"
|
<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">
|
||||||
|
|||||||
105
templates/objects/enzymelink.html
Normal file
105
templates/objects/enzymelink.html
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
{% extends "framework.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="panel-group" id="enzyme-detail">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
|
{{ enzymelink.ec_number }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a id="enzyme-name-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||||
|
href="#enzyme-name">Enzyme Name</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="enzyme-name" class="panel-collapse collapse in">
|
||||||
|
<div class="panel-body list-group-item">
|
||||||
|
{{ enzymelink.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Linking Method -->
|
||||||
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a id="enzyme-linking-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||||
|
href="#enzyme-linking">Linking Method</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="enzyme-linking" class="panel-collapse collapse in">
|
||||||
|
<div class="panel-body list-group-item">
|
||||||
|
{{ enzymelink.linking_method }}. <a
|
||||||
|
href="https://wiki.envipath.org/index.php/Rules#EnzymeLinks" target="#">Learn more >></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if enzymelink.kegg_reaction_links %}
|
||||||
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a id="enzyme-evidence-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||||
|
href="#enzyme-evidence">Linking Evidence</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="enzyme-evidence" class="panel-collapse collapse in">
|
||||||
|
<div class="panel-body list-group-item">
|
||||||
|
{% for kl in enzymelink.kegg_reaction_links %}
|
||||||
|
<a class="list-group-item"
|
||||||
|
href="{{ kl.external_url }}">{{ kl.identifier_value }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if enzymelink.reaction_evidence.all %}
|
||||||
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a id="enzyme-reaction-evidence-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||||
|
href="#enzyme-reaction-evidence">Linking Evidence - enviPath Reactions</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="enzyme-reaction-evidence" class="panel-collapse collapse in">
|
||||||
|
<div class="panel-body list-group-item">
|
||||||
|
{% for r in enzymelink.reaction_evidence.all %}
|
||||||
|
<a class="list-group-item" href="{{ r.url }}">{{ r.name }} <i>({{ r.package.name }})</i></a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if enzymelink.edge_evidence.all %}
|
||||||
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a id="enzyme-edge-evidence-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||||
|
href="#enzyme-edge-evidence">Linking Evidence - enviPath Pathways</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="enzyme-edge-evidence" class="panel-collapse collapse in">
|
||||||
|
<div class="panel-body list-group-item">
|
||||||
|
{% for e in enzymelink.edge_evidence.all %}
|
||||||
|
<a class="list-group-item" href="{{ e.pathway.url }}">{{ e.pathway.name }}
|
||||||
|
<i>({{ r.package.name }})</i></a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- External DB Reference -->
|
||||||
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a id="enzyme-external-identifier-link" data-toggle="collapse" data-parent="#enzyme-detail"
|
||||||
|
href="#enzyme-external-identifier">External DB References</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="enzyme-external-identifier" class="panel-collapse collapse in">
|
||||||
|
<div class="panel-body list-group-item">
|
||||||
|
<a class="list-group-item"
|
||||||
|
href="http://www.brenda-enzymes.org/enzyme.php?ecno={{ enzymelink.ec_number }}"
|
||||||
|
target="_blank"> Brenda entry for {{ enzymelink.ec_number }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)]
|
||||||
|
|||||||
35
tests/test_enviformer.py
Normal file
35
tests/test_enviformer.py
Normal file
@ -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")
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||||
|
)
|
||||||
|
|||||||
@ -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()
|
|
||||||
|
|||||||
396
tests/views/test_compound_views.py
Normal file
396
tests/views/test_compound_views.py
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from envipy_additional_information import Temperature, Interval
|
||||||
|
|
||||||
|
from epdb.logic import UserManager, PackageManager
|
||||||
|
from epdb.models import Compound, Scenario, ExternalDatabase
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundViewTest(TestCase):
|
||||||
|
fixtures = ["test_fixtures.jsonl.gz"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(CompoundViewTest, cls).setUpClass()
|
||||||
|
cls.user1 = UserManager.create_user(
|
||||||
|
"user1",
|
||||||
|
"user1@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
set_setting=False,
|
||||||
|
add_to_group=True,
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
cls.user1_default_package = cls.user1.default_package
|
||||||
|
cls.package = PackageManager.create_package(cls.user1, "Test", "Test Pack")
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client.force_login(self.user1)
|
||||||
|
|
||||||
|
def test_create_compound(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("compounds"),
|
||||||
|
{
|
||||||
|
"compound-name": "1,2-Dichloroethane",
|
||||||
|
"compound-description": "Eawag BBD compound c0001",
|
||||||
|
"compound-smiles": "C(CCl)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
compound_url = response.url
|
||||||
|
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
|
||||||
|
self.assertEqual(c.package, self.user1_default_package)
|
||||||
|
self.assertEqual(c.name, "1,2-Dichloroethane")
|
||||||
|
self.assertEqual(c.description, "Eawag BBD compound c0001")
|
||||||
|
self.assertEqual(c.default_structure.smiles, "C(CCl)Cl")
|
||||||
|
self.assertEqual(c.default_structure.canonical_smiles, "ClCCCl")
|
||||||
|
self.assertEqual(c.structures.all().count(), 2)
|
||||||
|
self.assertEqual(self.user1_default_package.compounds.count(), 1)
|
||||||
|
|
||||||
|
# Adding the same rule again should return the existing one, hence not increasing the number of rules
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("compounds"),
|
||||||
|
{
|
||||||
|
"compound-name": "1,2-Dichloroethane",
|
||||||
|
"compound-description": "Eawag BBD compound c0001",
|
||||||
|
"compound-smiles": "C(CCl)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.url, compound_url)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(self.user1_default_package.compounds.count(), 1)
|
||||||
|
|
||||||
|
# Adding the same rule in a different package should create a new rule
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("package compound list", kwargs={"package_uuid": self.package.uuid}),
|
||||||
|
{
|
||||||
|
"compound-name": "1,2-Dichloroethane",
|
||||||
|
"compound-description": "Eawag BBD compound c0001",
|
||||||
|
"compound-smiles": "C(CCl)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertNotEqual(compound_url, response.url)
|
||||||
|
|
||||||
|
# adding another reaction should increase count
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("compounds"),
|
||||||
|
{
|
||||||
|
"compound-name": "2-Chloroethanol",
|
||||||
|
"compound-description": "Eawag BBD compound c0005",
|
||||||
|
"compound-smiles": "C(CO)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(self.user1_default_package.compounds.count(), 2)
|
||||||
|
|
||||||
|
# Edit
|
||||||
|
def test_edit_rule(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("compounds"),
|
||||||
|
{
|
||||||
|
"compound-name": "1,2-Dichloroethane",
|
||||||
|
"compound-description": "Eawag BBD compound c0001",
|
||||||
|
"compound-smiles": "C(CCl)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
compound_url = response.url
|
||||||
|
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.user1_default_package.uuid),
|
||||||
|
"compound_uuid": str(c.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"compound-name": "Test Compound Adjusted",
|
||||||
|
"compound-description": "New Description",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
self.assertEqual(c.name, "Test Compound Adjusted")
|
||||||
|
self.assertEqual(c.description, "New Description")
|
||||||
|
|
||||||
|
# Rest stays untouched
|
||||||
|
self.assertEqual(c.default_structure.smiles, "C(CCl)Cl")
|
||||||
|
self.assertEqual(self.user1_default_package.compounds.count(), 1)
|
||||||
|
|
||||||
|
# Scenario
|
||||||
|
def test_set_scenario(self):
|
||||||
|
s1 = Scenario.create(
|
||||||
|
self.user1_default_package,
|
||||||
|
"Test Scen",
|
||||||
|
"Test Desc",
|
||||||
|
"2025-10",
|
||||||
|
"soil",
|
||||||
|
[Temperature(interval=Interval(start=20, end=30))],
|
||||||
|
)
|
||||||
|
|
||||||
|
s2 = Scenario.create(
|
||||||
|
self.user1_default_package,
|
||||||
|
"Test Scen2",
|
||||||
|
"Test Desc2",
|
||||||
|
"2025-10",
|
||||||
|
"soil",
|
||||||
|
[Temperature(interval=Interval(start=10, end=20))],
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("compounds"),
|
||||||
|
{
|
||||||
|
"compound-name": "1,2-Dichloroethane",
|
||||||
|
"compound-description": "Eawag BBD compound c0001",
|
||||||
|
"compound-smiles": "C(CCl)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
compound_url = response.url
|
||||||
|
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||||
|
),
|
||||||
|
{"selected-scenarios": [s1.url, s2.url]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(c.scenarios.all()), 2)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||||
|
),
|
||||||
|
{"selected-scenarios": [s1.url]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(c.scenarios.all()), 1)
|
||||||
|
self.assertEqual(c.scenarios.first().url, s1.url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
# We have to set an empty string to avoid that the parameter is removed
|
||||||
|
"selected-scenarios": ""
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(c.scenarios.all()), 0)
|
||||||
|
|
||||||
|
#
|
||||||
|
def test_copy(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("compounds"),
|
||||||
|
{
|
||||||
|
"compound-name": "1,2-Dichloroethane",
|
||||||
|
"compound-description": "Eawag BBD compound c0001",
|
||||||
|
"compound-smiles": "C(CCl)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
compound_url = response.url
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(self.package.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"hidden": "copy", "object_to_copy": c.url},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
copied_object_url = response.json()["success"]
|
||||||
|
copied_compound = Compound.objects.get(url=copied_object_url)
|
||||||
|
|
||||||
|
self.assertEqual(copied_compound.name, c.name)
|
||||||
|
self.assertEqual(copied_compound.description, c.description)
|
||||||
|
self.assertEqual(copied_compound.default_structure.smiles, c.default_structure.smiles)
|
||||||
|
|
||||||
|
# Copy to the same package should fail
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(c.package.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"hidden": "copy", "object_to_copy": c.url},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertEqual(
|
||||||
|
response.json()["error"], f"Can't copy object {compound_url} to the same package!"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_references(self):
|
||||||
|
ext_db, _ = ExternalDatabase.objects.get_or_create(
|
||||||
|
name="PubChem Compound",
|
||||||
|
defaults={
|
||||||
|
"full_name": "PubChem Compound Database",
|
||||||
|
"description": "Chemical database of small organic molecules",
|
||||||
|
"base_url": "https://pubchem.ncbi.nlm.nih.gov",
|
||||||
|
"url_pattern": "https://pubchem.ncbi.nlm.nih.gov/compound/{id}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ext_db2, _ = ExternalDatabase.objects.get_or_create(
|
||||||
|
name="PubChem Substance",
|
||||||
|
defaults={
|
||||||
|
"full_name": "PubChem Substance Database",
|
||||||
|
"description": "Database of chemical substances",
|
||||||
|
"base_url": "https://pubchem.ncbi.nlm.nih.gov",
|
||||||
|
"url_pattern": "https://pubchem.ncbi.nlm.nih.gov/substance/{id}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("compounds"),
|
||||||
|
{
|
||||||
|
"compound-name": "1,2-Dichloroethane",
|
||||||
|
"compound-description": "Eawag BBD compound c0001",
|
||||||
|
"compound-smiles": "C(CCl)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
compound_url = response.url
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(c.package.uuid),
|
||||||
|
"compound_uuid": str(c.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"selected-database": ext_db.pk, "identifier": "25154249"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(c.external_identifiers.count(), 1)
|
||||||
|
self.assertEqual(c.external_identifiers.first().database, ext_db)
|
||||||
|
self.assertEqual(c.external_identifiers.first().identifier_value, "25154249")
|
||||||
|
self.assertEqual(
|
||||||
|
c.external_identifiers.first().url, "https://pubchem.ncbi.nlm.nih.gov/compound/25154249"
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={
|
||||||
|
"package_uuid": str(c.package.uuid),
|
||||||
|
"compound_uuid": str(c.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{"selected-database": ext_db2.pk, "identifier": "25154249"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(c.external_identifiers.count(), 2)
|
||||||
|
self.assertEqual(c.external_identifiers.last().database, ext_db2)
|
||||||
|
self.assertEqual(c.external_identifiers.last().identifier_value, "25154249")
|
||||||
|
self.assertEqual(
|
||||||
|
c.external_identifiers.last().url, "https://pubchem.ncbi.nlm.nih.gov/substance/25154249"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("compounds"),
|
||||||
|
{
|
||||||
|
"compound-name": "1,2-Dichloroethane",
|
||||||
|
"compound-description": "Eawag BBD compound c0001",
|
||||||
|
"compound-smiles": "C(CCl)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
compound_url = response.url
|
||||||
|
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||||
|
),
|
||||||
|
{"hidden": "delete"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(self.user1_default_package.compounds.count(), 0)
|
||||||
|
|
||||||
|
def test_set_aliases(self):
|
||||||
|
alias_1 = "Alias 1"
|
||||||
|
alias_2 = "Alias 2"
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("compounds"),
|
||||||
|
{
|
||||||
|
"compound-name": "1,2-Dichloroethane",
|
||||||
|
"compound-description": "Eawag BBD compound c0001",
|
||||||
|
"compound-smiles": "C(CCl)Cl",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
compound_url = response.url
|
||||||
|
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||||
|
),
|
||||||
|
{"aliases": [alias_1, alias_2]},
|
||||||
|
)
|
||||||
|
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
self.assertEqual(len(c.aliases), 2)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||||
|
),
|
||||||
|
{"aliases": [alias_1]},
|
||||||
|
)
|
||||||
|
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
self.assertEqual(len(c.aliases), 1)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"package compound detail",
|
||||||
|
kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
# We have to set an empty string to avoid that the parameter is removed
|
||||||
|
"aliases": ""
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
c = Compound.objects.get(url=compound_url)
|
||||||
|
self.assertEqual(len(c.aliases), 0)
|
||||||
125
tests/views/test_model_views.py
Normal file
125
tests/views/test_model_views.py
Normal file
@ -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()
|
||||||
|
)
|
||||||
|
|||||||
168
tests/views/test_pathway_views.py
Normal file
168
tests/views/test_pathway_views.py
Normal file
@ -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)
|
||||||
390
tests/views/test_reaction_views.py
Normal file
390
tests/views/test_reaction_views.py
Normal file
@ -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)
|
||||||
314
tests/views/test_rule_views.py
Normal file
314
tests/views/test_rule_views.py
Normal file
@ -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
|
|
||||||
@ -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