forked from enviPath/enviPy
Compare commits
96 Commits
main
...
feature/te
| Author | SHA1 | Date | |
|---|---|---|---|
| 62e890245e | |||
| 138846d84d | |||
| 13ed86a780 | |||
| f1b4c5aadb | |||
| 37e0e18a28 | |||
| de44c22606 | |||
| a952c08469 | |||
| 551cfc7768 | |||
| 8fda2577ee | |||
| 819a94aced | |||
| 376fd65785 | |||
| d5ebb23622 | |||
| 93dd811e39 | |||
| 9a4735246f | |||
| 1f863fdcd6 | |||
| 1effaeb342 | |||
| 386098b8a6 | |||
| ef697ac5f5 | |||
| 68a3f3b982 | |||
| afeb56622c | |||
| 22f0bbe10b | |||
| 36879c266b | |||
| c2c46fbfa7 | |||
| d2f4fdc58a | |||
| 3f2b046bd6 | |||
| 7ad4112343 | |||
| 3f5bb76633 | |||
| b757a07f91 | |||
| b5c759d74e | |||
| b3079834c1 | |||
| f03ade0e94 | |||
| 50db2fb372 | |||
| 762a6b7baf | |||
| ce349a287b | |||
| 7905a8c2c1 | |||
| af3981d96e | |||
| 62e6448448 | |||
| 31783306e2 | |||
| e82fe7e87e | |||
| 4463bf1bc8 | |||
| abfd683640 | |||
| 3453a169e1 | |||
| 5477b5b3d4 | |||
| 1a6608287d | |||
| a16035677c | |||
| 498a53ab3d | |||
| aa3b53e94b | |||
| 3c8f0e80cb | |||
| 4158bd36cb | |||
| 4e02910c62 | |||
| 2babe7f7e2 | |||
| 7da3880a9b | |||
| 52931526c1 | |||
| dd7b28046c | |||
| 8592cfae50 | |||
| ec2b941a85 | |||
| 02d84a9b29 | |||
| 00d9188c0c | |||
| 13816ecaf3 | |||
| 6a4c8d96c3 | |||
| 97d0527565 | |||
| b45c99f7d3 | |||
| b95ec98a2f | |||
| c79a1f2040 | |||
| 6e6b394289 | |||
| ec387cc12e | |||
| a7637d046a | |||
| fc8192fb0d | |||
| c3c1d4f5cf | |||
| 3308d47071 | |||
| 1267ca8ace | |||
| ec52b8872d | |||
| 579cd519d0 | |||
| 280ddc7205 | |||
| c9d6d8b024 | |||
| a1aebfa54d | |||
| 79b4b1586c | |||
| aec61151ce | |||
| 026189ccd9 | |||
| 2c9f9038f3 | |||
| 2b8b6f286d | |||
| 2c4c9d95d9 | |||
| 43c95e3da7 | |||
| df896878f1 | |||
| 49e02ed97d | |||
| 4fff78541b | |||
| 9323a9f7d7 | |||
| 4e58a1fad7 | |||
| 9950112311 | |||
| 6eb1d1bd65 | |||
| bcd9451450 | |||
| 844d0708c9 | |||
| 7c3bc69b38 | |||
| 843e8e6f07 | |||
| acdb62c08f | |||
| ded50edaa2 |
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'
|
||||||
18
.env.prod.example
Normal file
18
.env.prod.example
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# settings.py
|
||||||
|
EP_DATA_DIR=
|
||||||
|
ALLOWED_HOSTS=
|
||||||
|
DEBUG=
|
||||||
|
LOG_LEVEL=
|
||||||
|
ENVIFORMER_PRESENT=
|
||||||
|
FLAG_CELERY_PRESENT=
|
||||||
|
SERVER_URL=
|
||||||
|
PLUGINS_ENABLED=
|
||||||
|
# DB
|
||||||
|
POSTGRES_SERVICE_NAME=
|
||||||
|
POSTGRES_DB=
|
||||||
|
POSTGRES_USER=
|
||||||
|
POSTGRES_PASSWORD=
|
||||||
|
POSTGRES_PORT=
|
||||||
|
# MAIL
|
||||||
|
EMAIL_HOST_USER=
|
||||||
|
EMAIL_HOST_PASSWORD=
|
||||||
180
.gitignore
vendored
180
.gitignore
vendored
@ -1,176 +1,12 @@
|
|||||||
# ---> Python
|
*.pyc
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
.idea/
|
||||||
|
static/admin/
|
||||||
# Flask stuff:
|
static/django_extensions/
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# UV
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
#uv.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
|
|
||||||
# pdm
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
||||||
#pdm.lock
|
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
||||||
# in version control.
|
|
||||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
||||||
.pdm.toml
|
|
||||||
.pdm-python
|
|
||||||
.pdm-build/
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
.env
|
||||||
.venv
|
debug.log
|
||||||
env/
|
scratches/
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
data/
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
|
||||||
|
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
||||||
#.idea/
|
|
||||||
|
|
||||||
# Ruff stuff:
|
|
||||||
.ruff_cache/
|
|
||||||
|
|
||||||
# PyPI configuration file
|
|
||||||
.pypirc
|
|
||||||
|
|
||||||
|
.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
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
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:
|
||||||
5
envipath/__init__.py
Normal file
5
envipath/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# This will make sure the app is always imported when
|
||||||
|
# Django starts so that shared_task will use this app.
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ("celery_app",)
|
||||||
12
envipath/api.py
Normal file
12
envipath/api.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from epdb.api import router as epdb_app_router
|
||||||
|
from epdb.legacy_api import router as epdb_legacy_app_router
|
||||||
|
from ninja import NinjaAPI
|
||||||
|
|
||||||
|
api = NinjaAPI()
|
||||||
|
|
||||||
|
api_v1 = NinjaAPI(title="API V1 Docs", urls_namespace="api-v1")
|
||||||
|
api_legacy = NinjaAPI(title="Legacy API Docs", urls_namespace="api-legacy")
|
||||||
|
|
||||||
|
# Add routers
|
||||||
|
api_v1.add_router("/", epdb_app_router)
|
||||||
|
api_legacy.add_router("/", epdb_legacy_app_router)
|
||||||
16
envipath/asgi.py
Normal file
16
envipath/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for envipath project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "envipath.settings")
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
27
envipath/celery.py
Normal file
27
envipath/celery.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from celery import Celery
|
||||||
|
from celery.signals import setup_logging
|
||||||
|
|
||||||
|
# Set the default Django settings module for the 'celery' program.
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "envipath.settings")
|
||||||
|
|
||||||
|
app = Celery("envipath")
|
||||||
|
|
||||||
|
# Using a string here means the worker doesn't have to serialize
|
||||||
|
# the configuration object to child processes.
|
||||||
|
# - namespace='CELERY' means all celery-related configuration keys
|
||||||
|
# should have a `CELERY_` prefix.
|
||||||
|
app.config_from_object("django.conf:settings", namespace="CELERY")
|
||||||
|
|
||||||
|
|
||||||
|
@setup_logging.connect
|
||||||
|
def config_loggers(*args, **kwargs):
|
||||||
|
from logging.config import dictConfig
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
dictConfig(settings.LOGGING)
|
||||||
|
|
||||||
|
|
||||||
|
# Load task modules from all registered Django apps.
|
||||||
|
app.autodiscover_tasks()
|
||||||
381
envipath/settings.py
Normal file
381
envipath/settings.py
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
"""
|
||||||
|
Django settings for envipath project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 4.2.17.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from envipy_plugins import Classifier, Property, Descriptor
|
||||||
|
from sklearn.ensemble import RandomForestClassifier
|
||||||
|
from sklearn.tree import DecisionTreeClassifier
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
load_dotenv(BASE_DIR / ".env", override=False)
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = os.environ.get("SECRET_KEY", "secret-key")
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = os.environ.get("DEBUG", "False") == "True"
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(",")
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
# 3rd party
|
||||||
|
"django_extensions",
|
||||||
|
"oauth2_provider",
|
||||||
|
# Custom
|
||||||
|
"epdb",
|
||||||
|
"migration",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Add the TENANT providing implementations for
|
||||||
|
# Required
|
||||||
|
# - Package
|
||||||
|
# - Compound (TODO)
|
||||||
|
# - CompoundStructure (TODO)
|
||||||
|
# Optional
|
||||||
|
# - PackageManager (TODO)
|
||||||
|
# - GroupManager (TODO)
|
||||||
|
# - SettingManager (TODO)
|
||||||
|
TENANT = os.environ.get("TENANT", "public")
|
||||||
|
INSTALLED_APPS.append(TENANT)
|
||||||
|
PACKAGE_IMPLEMENTATION = f"{TENANT}.Package"
|
||||||
|
PACKAGE_MODULE_PATH = f"{TENANT}.models.Package"
|
||||||
|
|
||||||
|
|
||||||
|
def GET_PACKAGE_MODEL():
|
||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
return apps.get_model(TENANT, "Package")
|
||||||
|
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
"oauth2_provider.middleware.OAuth2TokenMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
OAUTH2_PROVIDER = {
|
||||||
|
"PKCE_REQUIRED": False, # Accept PKCE requests but dont require them
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.environ.get("REGISTRATION_MANDATORY", False) == "True":
|
||||||
|
MIDDLEWARE.append("epdb.middleware.login_required_middleware.LoginRequiredMiddleware")
|
||||||
|
|
||||||
|
ROOT_URLCONF = "envipath.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": (os.path.join(BASE_DIR, "templates"),),
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "envipath.wsgi.application"
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
|
"USER": os.environ["POSTGRES_USER"],
|
||||||
|
"NAME": os.environ["POSTGRES_DB"],
|
||||||
|
"PASSWORD": os.environ["POSTGRES_PASSWORD"],
|
||||||
|
"HOST": os.environ["POSTGRES_SERVICE_NAME"],
|
||||||
|
"PORT": os.environ["POSTGRES_PORT"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
EMAIL_SUBJECT_PREFIX = "[enviPath] "
|
||||||
|
if DEBUG:
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
else:
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
EMAIL_HOST = "mail.gandi.net"
|
||||||
|
EMAIL_HOST_USER = os.environ["EMAIL_HOST_USER"]
|
||||||
|
EMAIL_HOST_PASSWORD = os.environ["EMAIL_HOST_PASSWORD"]
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
DEFAULT_FROM_EMAIL = os.environ["DEFAULT_FROM_EMAIL"]
|
||||||
|
SERVER_EMAIL = os.environ["SERVER_EMAIL"]
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = "epdb.User"
|
||||||
|
ADMIN_APPROVAL_REQUIRED = os.environ.get("ADMIN_APPROVAL_REQUIRED", "False") == "True"
|
||||||
|
|
||||||
|
# # SESAME
|
||||||
|
# SESAME_MAX_AGE = 300
|
||||||
|
# # TODO set to "home"
|
||||||
|
# LOGIN_REDIRECT_URL = "/"
|
||||||
|
LOGIN_URL = "/login/"
|
||||||
|
|
||||||
|
SERVER_URL = os.environ.get("SERVER_URL", "http://localhost:8000")
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = [SERVER_URL]
|
||||||
|
|
||||||
|
AMBIT_URL = "http://localhost:9001"
|
||||||
|
DEFAULT_VALUES = {"description": "no description"}
|
||||||
|
|
||||||
|
EP_DATA_DIR = os.environ["EP_DATA_DIR"]
|
||||||
|
if not os.path.exists(EP_DATA_DIR):
|
||||||
|
os.mkdir(EP_DATA_DIR)
|
||||||
|
|
||||||
|
MODEL_DIR = os.path.join(EP_DATA_DIR, "models")
|
||||||
|
if not os.path.exists(MODEL_DIR):
|
||||||
|
os.mkdir(MODEL_DIR)
|
||||||
|
|
||||||
|
STATIC_DIR = os.path.join(EP_DATA_DIR, "static")
|
||||||
|
if not os.path.exists(STATIC_DIR):
|
||||||
|
os.mkdir(STATIC_DIR)
|
||||||
|
|
||||||
|
LOG_DIR = os.path.join(EP_DATA_DIR, "log")
|
||||||
|
if not os.path.exists(LOG_DIR):
|
||||||
|
os.mkdir(LOG_DIR)
|
||||||
|
|
||||||
|
PLUGIN_DIR = os.path.join(EP_DATA_DIR, "plugins")
|
||||||
|
if not os.path.exists(PLUGIN_DIR):
|
||||||
|
os.mkdir(PLUGIN_DIR)
|
||||||
|
|
||||||
|
# Set this as our static root dir
|
||||||
|
STATIC_ROOT = STATIC_DIR
|
||||||
|
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
|
# Where the sources are stored...
|
||||||
|
STATICFILES_DIRS = (BASE_DIR / "static",)
|
||||||
|
|
||||||
|
FIXTURE_DIRS = (BASE_DIR / "fixtures",)
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": True,
|
||||||
|
"formatters": {
|
||||||
|
"simple": {
|
||||||
|
"format": "[%(asctime)s] %(levelname)s %(module)s - %(message)s",
|
||||||
|
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"level": "INFO",
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"formatter": "simple",
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"level": "DEBUG", # Or higher
|
||||||
|
"class": "logging.FileHandler",
|
||||||
|
"filename": os.path.join(LOG_DIR, "debug.log"),
|
||||||
|
"formatter": "simple",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
# For everything under epdb/ loaded via getlogger(__name__)
|
||||||
|
"epdb": {
|
||||||
|
"handlers": ["file"], # "console",
|
||||||
|
"propagate": True,
|
||||||
|
"level": os.environ.get("LOG_LEVEL", "INFO"),
|
||||||
|
},
|
||||||
|
# For everything under envipath/ loaded via getlogger(__name__)
|
||||||
|
"envipath": {
|
||||||
|
"handlers": ["file", "console"],
|
||||||
|
"propagate": True,
|
||||||
|
"level": os.environ.get("LOG_LEVEL", "INFO"),
|
||||||
|
},
|
||||||
|
# For everything under utilities/ loaded via getlogger(__name__)
|
||||||
|
"utilities": {
|
||||||
|
"handlers": ["file", "console"],
|
||||||
|
"propagate": True,
|
||||||
|
"level": os.environ.get("LOG_LEVEL", "INFO"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Flags
|
||||||
|
ENVIFORMER_PRESENT = os.environ.get("ENVIFORMER_PRESENT", "False") == "True"
|
||||||
|
ENVIFORMER_DEVICE = os.environ.get("ENVIFORMER_DEVICE", "cpu")
|
||||||
|
|
||||||
|
# If celery is not present set always eager to true which will cause delayed tasks to block until finished
|
||||||
|
FLAG_CELERY_PRESENT = os.environ.get("FLAG_CELERY_PRESENT", "False") == "True"
|
||||||
|
if not FLAG_CELERY_PRESENT:
|
||||||
|
CELERY_TASK_ALWAYS_EAGER = True
|
||||||
|
|
||||||
|
# Celery Configuration Options
|
||||||
|
CELERY_TIMEZONE = "Europe/Berlin"
|
||||||
|
# Celery Configuration
|
||||||
|
CELERY_BROKER_URL = "redis://localhost:6379/0" # Use Redis as message broker
|
||||||
|
CELERY_RESULT_BACKEND = "redis://localhost:6379/1"
|
||||||
|
CELERY_ACCEPT_CONTENT = ["json"]
|
||||||
|
CELERY_TASK_SERIALIZER = "json"
|
||||||
|
|
||||||
|
MODEL_BUILDING_ENABLED = os.environ.get("MODEL_BUILDING_ENABLED", "False") == "True"
|
||||||
|
APPLICABILITY_DOMAIN_ENABLED = os.environ.get("APPLICABILITY_DOMAIN_ENABLED", "False") == "True"
|
||||||
|
DEFAULT_RF_MODEL_PARAMS = {
|
||||||
|
"base_clf": RandomForestClassifier(
|
||||||
|
n_estimators=100,
|
||||||
|
max_features="log2",
|
||||||
|
random_state=42,
|
||||||
|
criterion="entropy",
|
||||||
|
ccp_alpha=0.0,
|
||||||
|
max_depth=3,
|
||||||
|
min_samples_leaf=1,
|
||||||
|
),
|
||||||
|
"num_chains": 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_MODEL_PARAMS = {
|
||||||
|
"base_clf": DecisionTreeClassifier(
|
||||||
|
criterion="entropy",
|
||||||
|
max_depth=3,
|
||||||
|
min_samples_split=5,
|
||||||
|
# min_samples_leaf=5,
|
||||||
|
max_features="sqrt",
|
||||||
|
# class_weight='balanced',
|
||||||
|
random_state=42,
|
||||||
|
),
|
||||||
|
"num_chains": 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_MAX_NUMBER_OF_NODES = 30
|
||||||
|
DEFAULT_MAX_DEPTH = 5
|
||||||
|
DEFAULT_MODEL_THRESHOLD = 0.25
|
||||||
|
|
||||||
|
# Loading Plugins
|
||||||
|
PLUGINS_ENABLED = os.environ.get("PLUGINS_ENABLED", "False") == "True"
|
||||||
|
if PLUGINS_ENABLED:
|
||||||
|
from utilities.plugin import discover_plugins
|
||||||
|
|
||||||
|
CLASSIFIER_PLUGINS = discover_plugins(_cls=Classifier)
|
||||||
|
PROPERTY_PLUGINS = discover_plugins(_cls=Property)
|
||||||
|
DESCRIPTOR_PLUGINS = discover_plugins(_cls=Descriptor)
|
||||||
|
else:
|
||||||
|
CLASSIFIER_PLUGINS = {}
|
||||||
|
PROPERTY_PLUGINS = {}
|
||||||
|
DESCRIPTOR_PLUGINS = {}
|
||||||
|
|
||||||
|
SENTRY_ENABLED = os.environ.get("SENTRY_ENABLED", "False") == "True"
|
||||||
|
if SENTRY_ENABLED:
|
||||||
|
import sentry_sdk
|
||||||
|
|
||||||
|
def before_send(event, hint):
|
||||||
|
# Check if was a handled exception by one of our loggers
|
||||||
|
if event.get("logger"):
|
||||||
|
for log_path in LOGGING.get("loggers").keys():
|
||||||
|
if event["logger"].startswith(log_path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
sentry_sdk.init(
|
||||||
|
dsn=os.environ.get("SENTRY_DSN"),
|
||||||
|
# Add data like request headers and IP for users,
|
||||||
|
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
|
||||||
|
send_default_pii=True,
|
||||||
|
environment=os.environ.get("SENTRY_ENVIRONMENT", "development"),
|
||||||
|
before_send=before_send,
|
||||||
|
)
|
||||||
|
|
||||||
|
# compile into digestible flags
|
||||||
|
FLAGS = {
|
||||||
|
"MODEL_BUILDING": MODEL_BUILDING_ENABLED,
|
||||||
|
"CELERY": FLAG_CELERY_PRESENT,
|
||||||
|
"PLUGINS": PLUGINS_ENABLED,
|
||||||
|
"SENTRY": SENTRY_ENABLED,
|
||||||
|
"ENVIFORMER": ENVIFORMER_PRESENT,
|
||||||
|
"APPLICABILITY_DOMAIN": APPLICABILITY_DOMAIN_ENABLED,
|
||||||
|
}
|
||||||
|
|
||||||
|
# path of the URL are checked via "startswith"
|
||||||
|
# -> /password_reset/done is covered as well
|
||||||
|
LOGIN_EXEMPT_URLS = [
|
||||||
|
"/register",
|
||||||
|
"/api/legacy/",
|
||||||
|
"/o/token/",
|
||||||
|
"/o/userinfo/",
|
||||||
|
"/password_reset/",
|
||||||
|
"/reset/",
|
||||||
|
"/microsoft/",
|
||||||
|
]
|
||||||
|
|
||||||
|
# MS AD/Entra
|
||||||
|
MS_ENTRA_ENABLED = os.environ.get("MS_ENTRA_ENABLED", "False") == "True"
|
||||||
|
if MS_ENTRA_ENABLED:
|
||||||
|
# Add app to installed apps
|
||||||
|
INSTALLED_APPS.append("epauth")
|
||||||
|
# Set vars required by app
|
||||||
|
MS_ENTRA_CLIENT_ID = os.environ["MS_CLIENT_ID"]
|
||||||
|
MS_ENTRA_CLIENT_SECRET = os.environ["MS_CLIENT_SECRET"]
|
||||||
|
MS_ENTRA_TENANT_ID = os.environ["MS_TENANT_ID"]
|
||||||
|
MS_ENTRA_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}"
|
||||||
|
MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"]
|
||||||
|
MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",")
|
||||||
34
envipath/urls.py
Normal file
34
envipath/urls.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for envipath project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
from .api import api_v1, api_legacy
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", include("epdb.urls")),
|
||||||
|
path("", include("migration.urls")),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("api/v1/", api_v1.urls),
|
||||||
|
path("api/legacy/", api_legacy.urls),
|
||||||
|
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
||||||
|
]
|
||||||
|
|
||||||
|
if s.MS_ENTRA_ENABLED:
|
||||||
|
urlpatterns.append(path("", include("epauth.urls")))
|
||||||
16
envipath/wsgi.py
Normal file
16
envipath/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for envipath project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "envipath.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
0
epauth/__init__.py
Normal file
0
epauth/__init__.py
Normal file
3
epauth/admin.py
Normal file
3
epauth/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
epauth/apps.py
Normal file
6
epauth/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class EpauthConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'epauth'
|
||||||
0
epauth/migrations/__init__.py
Normal file
0
epauth/migrations/__init__.py
Normal file
3
epauth/models.py
Normal file
3
epauth/models.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
epauth/tests.py
Normal file
3
epauth/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
8
epauth/urls.py
Normal file
8
epauth/urls.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("microsoft/login/", views.microsoft_login, name="microsoft_login"),
|
||||||
|
path("microsoft/callback/", views.microsoft_callback, name="microsoft_callback"),
|
||||||
|
]
|
||||||
66
epauth/views.py
Normal file
66
epauth/views.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import msal
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.contrib.auth import login
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from epdb.logic import UserManager
|
||||||
|
|
||||||
|
|
||||||
|
def microsoft_login(request):
|
||||||
|
msal_app = msal.ConfidentialClientApplication(
|
||||||
|
client_id=s.MS_ENTRA_CLIENT_ID,
|
||||||
|
client_credential=s.MS_ENTRA_CLIENT_SECRET,
|
||||||
|
authority=s.MS_ENTRA_AUTHORITY
|
||||||
|
)
|
||||||
|
|
||||||
|
flow = msal_app.initiate_auth_code_flow(
|
||||||
|
scopes=s.MS_ENTRA_SCOPES,
|
||||||
|
redirect_uri=s.MS_ENTRA_REDIRECT_URI
|
||||||
|
)
|
||||||
|
|
||||||
|
request.session["msal_auth_flow"] = flow
|
||||||
|
return redirect(flow["auth_uri"])
|
||||||
|
|
||||||
|
|
||||||
|
def microsoft_callback(request):
|
||||||
|
msal_app = msal.ConfidentialClientApplication(
|
||||||
|
client_id=s.MS_ENTRA_CLIENT_ID,
|
||||||
|
client_credential=s.MS_ENTRA_CLIENT_SECRET,
|
||||||
|
authority=s.MS_ENTRA_AUTHORITY
|
||||||
|
)
|
||||||
|
|
||||||
|
flow = request.session.pop("msal_auth_flow", None)
|
||||||
|
if not flow:
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
# Acquire token using the flow and callback request
|
||||||
|
result = msal_app.acquire_token_by_auth_code_flow(flow, request.GET)
|
||||||
|
|
||||||
|
if "access_token" in result:
|
||||||
|
# Optional: Fetch user info from Microsoft Graph
|
||||||
|
import requests
|
||||||
|
resp = requests.get(
|
||||||
|
"https://graph.microsoft.com/v1.0/me",
|
||||||
|
headers={"Authorization": f"Bearer {result['access_token']}"}
|
||||||
|
)
|
||||||
|
user_info = resp.json()
|
||||||
|
|
||||||
|
user_name = user_info["displayName"]
|
||||||
|
user_email = user_info["mail"]
|
||||||
|
user_oid = user_info["id"]
|
||||||
|
|
||||||
|
# Get implementing class
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
if User.objects.filter(uuid=user_oid).exists():
|
||||||
|
login(request, User.objects.get(uuid=user_oid))
|
||||||
|
else:
|
||||||
|
u = UserManager.create_user(user_name, user_email, None, uuid=user_oid, is_active=True)
|
||||||
|
login(request, u)
|
||||||
|
|
||||||
|
# TODO Group Sync
|
||||||
|
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
return redirect("/") # Handle errors
|
||||||
0
epdb/__init__.py
Normal file
0
epdb/__init__.py
Normal file
135
epdb/admin.py
Normal file
135
epdb/admin.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.conf import settings as s
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
User,
|
||||||
|
UserPackagePermission,
|
||||||
|
Group,
|
||||||
|
GroupPackagePermission,
|
||||||
|
MLRelativeReasoning,
|
||||||
|
EnviFormer,
|
||||||
|
Compound,
|
||||||
|
CompoundStructure,
|
||||||
|
SimpleAmbitRule,
|
||||||
|
ParallelRule,
|
||||||
|
Reaction,
|
||||||
|
Pathway,
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
Scenario,
|
||||||
|
Setting,
|
||||||
|
ExternalDatabase,
|
||||||
|
ExternalIdentifier,
|
||||||
|
JobLog,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
|
class UserAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["username", "email", "is_active"]
|
||||||
|
|
||||||
|
|
||||||
|
class UserPackagePermissionAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GroupAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GroupPackagePermissionAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JobLogAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EPAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ["name", "description"]
|
||||||
|
list_display = ["name", "url", "created"]
|
||||||
|
ordering = ["-created"]
|
||||||
|
|
||||||
|
|
||||||
|
class PackageAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MLRelativeReasoningAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EnviFormerAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundStructureAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleAmbitRuleAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ParallelRuleAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ReactionAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PathwayAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NodeAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ScenarioAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SettingAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalDatabaseAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalIdentifierAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(User, UserAdmin)
|
||||||
|
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
|
||||||
|
admin.site.register(Group, GroupAdmin)
|
||||||
|
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
|
||||||
|
admin.site.register(JobLog, JobLogAdmin)
|
||||||
|
admin.site.register(Package, PackageAdmin)
|
||||||
|
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
||||||
|
admin.site.register(EnviFormer, EnviFormerAdmin)
|
||||||
|
admin.site.register(Compound, CompoundAdmin)
|
||||||
|
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
||||||
|
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
||||||
|
admin.site.register(ParallelRule, ParallelRuleAdmin)
|
||||||
|
admin.site.register(Reaction, ReactionAdmin)
|
||||||
|
admin.site.register(Pathway, PathwayAdmin)
|
||||||
|
admin.site.register(Node, NodeAdmin)
|
||||||
|
admin.site.register(Edge, EdgeAdmin)
|
||||||
|
admin.site.register(Setting, SettingAdmin)
|
||||||
|
admin.site.register(Scenario, ScenarioAdmin)
|
||||||
|
admin.site.register(ExternalDatabase, ExternalDatabaseAdmin)
|
||||||
|
admin.site.register(ExternalIdentifier, ExternalIdentifierAdmin)
|
||||||
113
epdb/api.py
Normal file
113
epdb/api.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from ninja import Router, Schema, Field
|
||||||
|
from ninja.errors import HttpError
|
||||||
|
from ninja.pagination import paginate
|
||||||
|
from ninja.security import HttpBearer
|
||||||
|
|
||||||
|
from .logic import PackageManager
|
||||||
|
from .models import User, Compound, APIToken
|
||||||
|
|
||||||
|
|
||||||
|
class BearerTokenAuth(HttpBearer):
|
||||||
|
def authenticate(self, request, token):
|
||||||
|
for token_obj in APIToken.objects.select_related("user").all():
|
||||||
|
if token_obj.check_token(token) and token_obj.is_valid():
|
||||||
|
return token_obj.user
|
||||||
|
raise HttpError(401, "Invalid or expired token")
|
||||||
|
|
||||||
|
|
||||||
|
def _anonymous_or_real(request):
|
||||||
|
if request.user.is_authenticated and not request.user.is_anonymous:
|
||||||
|
return request.user
|
||||||
|
return get_user_model().objects.get(username="anonymous")
|
||||||
|
|
||||||
|
|
||||||
|
router = Router(auth=BearerTokenAuth())
|
||||||
|
|
||||||
|
|
||||||
|
class UserSchema(Schema):
|
||||||
|
email: str
|
||||||
|
username: str
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
|
||||||
|
|
||||||
|
class PackageIn(Schema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
class PackageOut(Schema):
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
name: str
|
||||||
|
reviewed: bool
|
||||||
|
compound_links: str = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_compound_links(obj):
|
||||||
|
return f"{obj.url}/compound"
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Schema):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundSchema(Schema):
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
smiles: str = Field(None, alias="default_structure.smiles")
|
||||||
|
reviewed: bool = Field(None, alias="package.reviewed")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user", response={200: List[UserSchema], 403: Error})
|
||||||
|
def get_users(request):
|
||||||
|
return User.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package", response={200: List[PackageOut], 403: Error})
|
||||||
|
def get_packages(request):
|
||||||
|
return PackageManager.get_all_readable_packages(request.user, include_reviewed=True)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package", response=PackageOut)
|
||||||
|
def create_package(request, package: PackageIn):
|
||||||
|
user = request.auth
|
||||||
|
name = package.name.strip()
|
||||||
|
description = package.description.strip()
|
||||||
|
|
||||||
|
p = PackageManager.create_package(user, name, description=description)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}", response={200: PackageOut, 403: Error})
|
||||||
|
def get_package(request, package_uuid):
|
||||||
|
try:
|
||||||
|
return PackageManager.get_package_by_id(request.auth, package_id=package_uuid)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Getting Package with id {package_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/compound", response={200: List[CompoundSchema], 403: Error})
|
||||||
|
@paginate
|
||||||
|
def get_compounds(request):
|
||||||
|
qs = Compound.objects.none()
|
||||||
|
for p in PackageManager.get_reviewed_packages():
|
||||||
|
qs |= Compound.objects.filter(package=p)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/package/{uuid:package_uuid}/compound", response={200: List[CompoundSchema], 403: Error}
|
||||||
|
)
|
||||||
|
@paginate
|
||||||
|
def get_package_compounds(request, package_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.auth, package_uuid)
|
||||||
|
return Compound.objects.filter(package=p)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Getting Compounds for Package with id {package_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
9
epdb/apps.py
Normal file
9
epdb/apps.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class EPDBConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "epdb"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import epdb.signals # noqa: F401
|
||||||
1243
epdb/legacy_api.py
Normal file
1243
epdb/legacy_api.py
Normal file
File diff suppressed because it is too large
Load Diff
1685
epdb/logic.py
Normal file
1685
epdb/logic.py
Normal file
File diff suppressed because it is too large
Load Diff
0
epdb/management/__init__.py
Normal file
0
epdb/management/__init__.py
Normal file
0
epdb/management/commands/__init__.py
Normal file
0
epdb/management/commands/__init__.py
Normal file
224
epdb/management/commands/bootstrap.py
Normal file
224
epdb/management/commands/bootstrap.py
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.logic import UserManager, GroupManager, PackageManager, SettingManager
|
||||||
|
from epdb.models import (
|
||||||
|
UserSettingPermission,
|
||||||
|
MLRelativeReasoning,
|
||||||
|
EnviFormer,
|
||||||
|
Permission,
|
||||||
|
User,
|
||||||
|
ExternalDatabase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def create_users(self):
|
||||||
|
# Anonymous User
|
||||||
|
if not User.objects.filter(email="anon@envipath.com").exists():
|
||||||
|
anon = UserManager.create_user(
|
||||||
|
"anonymous",
|
||||||
|
"anon@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
is_active=True,
|
||||||
|
add_to_group=False,
|
||||||
|
set_setting=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
anon = User.objects.get(email="anon@envipath.com")
|
||||||
|
|
||||||
|
# Admin User
|
||||||
|
if not User.objects.filter(email="admin@envipath.com").exists():
|
||||||
|
admin = UserManager.create_user(
|
||||||
|
"admin",
|
||||||
|
"admin@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
is_active=True,
|
||||||
|
add_to_group=False,
|
||||||
|
set_setting=False,
|
||||||
|
)
|
||||||
|
admin.is_staff = True
|
||||||
|
admin.is_superuser = True
|
||||||
|
admin.save()
|
||||||
|
else:
|
||||||
|
admin = User.objects.get(email="admin@envipath.com")
|
||||||
|
|
||||||
|
# System Group
|
||||||
|
g = GroupManager.create_group(admin, "enviPath Users", "All enviPath Users")
|
||||||
|
g.public = True
|
||||||
|
g.save()
|
||||||
|
|
||||||
|
g.user_member.add(anon)
|
||||||
|
g.save()
|
||||||
|
|
||||||
|
anon.default_group = g
|
||||||
|
anon.save()
|
||||||
|
|
||||||
|
admin.default_group = g
|
||||||
|
admin.save()
|
||||||
|
|
||||||
|
if not User.objects.filter(email="user0@envipath.com").exists():
|
||||||
|
user0 = UserManager.create_user(
|
||||||
|
"user0",
|
||||||
|
"user0@envipath.com",
|
||||||
|
"SuperSafe",
|
||||||
|
is_active=True,
|
||||||
|
add_to_group=False,
|
||||||
|
set_setting=False,
|
||||||
|
)
|
||||||
|
user0.is_staff = True
|
||||||
|
user0.is_superuser = True
|
||||||
|
user0.save()
|
||||||
|
else:
|
||||||
|
user0 = User.objects.get(email="user0@envipath.com")
|
||||||
|
|
||||||
|
g.user_member.add(user0)
|
||||||
|
g.save()
|
||||||
|
|
||||||
|
user0.default_group = g
|
||||||
|
user0.save()
|
||||||
|
|
||||||
|
return anon, admin, g, user0
|
||||||
|
|
||||||
|
def import_package(self, data, owner):
|
||||||
|
return PackageManager.import_legacy_package(
|
||||||
|
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_default_setting(self, owner, packages):
|
||||||
|
s = SettingManager.create_setting(
|
||||||
|
owner,
|
||||||
|
name="Global Default Setting",
|
||||||
|
description="Global Default Setting containing BBD Rules and Max 30 Nodes and Max Depth of 8",
|
||||||
|
max_nodes=30,
|
||||||
|
max_depth=5,
|
||||||
|
rule_packages=packages,
|
||||||
|
model=None,
|
||||||
|
model_threshold=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
def populate_common_external_databases(self):
|
||||||
|
"""
|
||||||
|
Helper function to populate common external databases.
|
||||||
|
This can be called from a Django management command.
|
||||||
|
"""
|
||||||
|
databases = [
|
||||||
|
{
|
||||||
|
"name": "PubChem Compound",
|
||||||
|
"full_name": "PubChem Compound Database",
|
||||||
|
"description": "Chemical database of small organic molecules",
|
||||||
|
"base_url": "https://pubchem.ncbi.nlm.nih.gov",
|
||||||
|
"url_pattern": "https://pubchem.ncbi.nlm.nih.gov/compound/{id}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PubChem Substance",
|
||||||
|
"full_name": "PubChem Substance Database",
|
||||||
|
"description": "Database of chemical substances",
|
||||||
|
"base_url": "https://pubchem.ncbi.nlm.nih.gov",
|
||||||
|
"url_pattern": "https://pubchem.ncbi.nlm.nih.gov/substance/{id}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ChEBI",
|
||||||
|
"full_name": "Chemical Entities of Biological Interest",
|
||||||
|
"description": "Dictionary of molecular entities",
|
||||||
|
"base_url": "https://www.ebi.ac.uk/chebi",
|
||||||
|
"url_pattern": "https://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:{id}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RHEA",
|
||||||
|
"full_name": "RHEA Reaction Database",
|
||||||
|
"description": "Comprehensive resource of biochemical reactions",
|
||||||
|
"base_url": "https://www.rhea-db.org",
|
||||||
|
"url_pattern": "https://www.rhea-db.org/rhea/{id}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "KEGG Reaction",
|
||||||
|
"full_name": "KEGG Reaction Database",
|
||||||
|
"description": "Database of biochemical reactions",
|
||||||
|
"base_url": "https://www.genome.jp",
|
||||||
|
"url_pattern": "https://www.genome.jp/entry/{id}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "UniProt",
|
||||||
|
"full_name": "MetaCyc Metabolic Pathway Database",
|
||||||
|
"description": "UniProt is a freely accessible database of protein sequence and functional information",
|
||||||
|
"base_url": "https://www.uniprot.org",
|
||||||
|
"url_pattern": 'https://www.uniprot.org/uniprotkb?query="{id}"',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for db_info in databases:
|
||||||
|
ExternalDatabase.objects.get_or_create(name=db_info["name"], defaults=db_info)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
# Create users
|
||||||
|
anon, admin, g, user0 = self.create_users()
|
||||||
|
|
||||||
|
self.populate_common_external_databases()
|
||||||
|
|
||||||
|
# Import Packages
|
||||||
|
packages = [
|
||||||
|
"EAWAG-BBD.json",
|
||||||
|
"EAWAG-SOIL.json",
|
||||||
|
"EAWAG-SLUDGE.json",
|
||||||
|
"EAWAG-SEDIMENT.json",
|
||||||
|
]
|
||||||
|
|
||||||
|
mapping = {}
|
||||||
|
for p in packages:
|
||||||
|
print(f"Importing {p}...")
|
||||||
|
package_data = json.loads(
|
||||||
|
open(
|
||||||
|
s.BASE_DIR / "fixtures" / "packages" / "2025-07-18" / p, encoding="utf-8"
|
||||||
|
).read()
|
||||||
|
)
|
||||||
|
imported_package = self.import_package(package_data, admin)
|
||||||
|
mapping[p.replace(".json", "")] = imported_package
|
||||||
|
|
||||||
|
setting = self.create_default_setting(admin, [mapping["EAWAG-BBD"]])
|
||||||
|
setting.public = True
|
||||||
|
setting.save()
|
||||||
|
setting.make_global_default()
|
||||||
|
|
||||||
|
for u in [anon, user0]:
|
||||||
|
u.default_setting = setting
|
||||||
|
u.save()
|
||||||
|
|
||||||
|
usp = UserSettingPermission()
|
||||||
|
usp.user = u
|
||||||
|
usp.setting = setting
|
||||||
|
usp.permission = Permission.READ[0]
|
||||||
|
usp.save()
|
||||||
|
|
||||||
|
# Create Model Package
|
||||||
|
pack = PackageManager.create_package(
|
||||||
|
admin,
|
||||||
|
"Public Prediction Models",
|
||||||
|
"Package to make Prediction Models publicly available",
|
||||||
|
)
|
||||||
|
pack.reviewed = True
|
||||||
|
pack.save()
|
||||||
|
|
||||||
|
# Create RR
|
||||||
|
ml_model = MLRelativeReasoning.create(
|
||||||
|
package=pack,
|
||||||
|
rule_packages=[mapping["EAWAG-BBD"]],
|
||||||
|
data_packages=[mapping["EAWAG-BBD"]],
|
||||||
|
eval_packages=[],
|
||||||
|
threshold=0.5,
|
||||||
|
name="ECC - BBD - T0.5",
|
||||||
|
description="ML Relative Reasoning",
|
||||||
|
)
|
||||||
|
|
||||||
|
ml_model.build_dataset()
|
||||||
|
ml_model.build_model()
|
||||||
|
|
||||||
|
# If available, create EnviFormerModel
|
||||||
|
if s.ENVIFORMER_PRESENT:
|
||||||
|
EnviFormer.create(pack, "EnviFormer - T0.5", "EnviFormer Model with Threshold 0.5", 0.5)
|
||||||
123
epdb/management/commands/create_ml_models.py
Normal file
123
epdb/management/commands/create_ml_models.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
from django.conf import settings as s
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.models import EnviFormer, MLRelativeReasoning
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""This command can be run with
|
||||||
|
`python manage.py create_ml_models [model_names] -d [data_packages] FOR MLRR ONLY: -r [rule_packages]
|
||||||
|
OPTIONAL: -e [eval_packages] -t threshold`
|
||||||
|
For example, to train both EnviFormer and MLRelativeReasoning on BBD and SOIL and evaluate them on SLUDGE with a
|
||||||
|
threshold of 0.6, the below command would be used:
|
||||||
|
`python manage.py create_ml_models enviformer mlrr -d bbd soil -e sludge -t 0.6
|
||||||
|
"""
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"model_names",
|
||||||
|
nargs="+",
|
||||||
|
type=str,
|
||||||
|
help="The names of models to train. Options are: enviformer, mlrr",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d", "--data-packages", nargs="+", type=str, help="Packages for training"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-e", "--eval-packages", nargs="*", type=str, help="Packages for evaluation", default=[]
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-r",
|
||||||
|
"--rule-packages",
|
||||||
|
nargs="*",
|
||||||
|
type=str,
|
||||||
|
help="Rule Packages mandatory for MLRR",
|
||||||
|
default=[],
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--threshold",
|
||||||
|
type=float,
|
||||||
|
help="Model prediction threshold",
|
||||||
|
default=0.5,
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
# Find Public Prediction Models package to add new models to
|
||||||
|
try:
|
||||||
|
pack = Package.objects.filter(name="Public Prediction Models")[0]
|
||||||
|
bbd = Package.objects.filter(name="EAWAG-BBD")[0]
|
||||||
|
soil = Package.objects.filter(name="EAWAG-SOIL")[0]
|
||||||
|
sludge = Package.objects.filter(name="EAWAG-SLUDGE")[0]
|
||||||
|
sediment = Package.objects.filter(name="EAWAG-SEDIMENT")[0]
|
||||||
|
except IndexError:
|
||||||
|
raise IndexError(
|
||||||
|
"Can't find correct packages. They should be created with the bootstrap command"
|
||||||
|
)
|
||||||
|
|
||||||
|
def decode_packages(package_list):
|
||||||
|
"""Decode package strings into their respective packages"""
|
||||||
|
packages = []
|
||||||
|
for p in package_list:
|
||||||
|
p = p.lower()
|
||||||
|
if p == "bbd":
|
||||||
|
packages.append(bbd)
|
||||||
|
elif p == "soil":
|
||||||
|
packages.append(soil)
|
||||||
|
elif p == "sludge":
|
||||||
|
packages.append(sludge)
|
||||||
|
elif p == "sediment":
|
||||||
|
packages.append(sediment)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown package {p}")
|
||||||
|
return packages
|
||||||
|
|
||||||
|
# Iteratively create models in options["model_names"]
|
||||||
|
print(
|
||||||
|
f"Creating models: {options['model_names']}\n"
|
||||||
|
f"Data packages: {options['data_packages']}\n"
|
||||||
|
f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
|
||||||
|
f"Eval Packages: {options['eval_packages']}\n"
|
||||||
|
f"Threshold: {options['threshold']:.2f}"
|
||||||
|
)
|
||||||
|
data_packages = decode_packages(options["data_packages"])
|
||||||
|
eval_packages = decode_packages(options["eval_packages"])
|
||||||
|
rule_packages = decode_packages(options["rule_packages"])
|
||||||
|
for model_name in options["model_names"]:
|
||||||
|
model_name = model_name.lower()
|
||||||
|
if model_name == "enviformer" and s.ENVIFORMER_PRESENT:
|
||||||
|
model = EnviFormer.create(
|
||||||
|
pack,
|
||||||
|
data_packages=data_packages,
|
||||||
|
eval_packages=eval_packages,
|
||||||
|
threshold=options["threshold"],
|
||||||
|
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||||
|
description=f"EnviFormer transformer trained on {options['data_packages']} "
|
||||||
|
f"evaluated on {options['eval_packages']}.",
|
||||||
|
)
|
||||||
|
elif model_name == "mlrr":
|
||||||
|
model = MLRelativeReasoning.create(
|
||||||
|
package=pack,
|
||||||
|
rule_packages=rule_packages,
|
||||||
|
data_packages=data_packages,
|
||||||
|
eval_packages=eval_packages,
|
||||||
|
threshold=options["threshold"],
|
||||||
|
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||||
|
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
|
||||||
|
f"{options['rule_packages']} and evaluated on {options['eval_packages']}.",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Cannot create model of type {model_name}, unknown model type")
|
||||||
|
# Build the dataset for the model, train it, evaluate it and save it
|
||||||
|
print(f"Building dataset for {model_name}")
|
||||||
|
model.build_dataset()
|
||||||
|
print(f"Training {model_name}")
|
||||||
|
model.build_model()
|
||||||
|
print(f"Evaluating {model_name}")
|
||||||
|
model.evaluate_model(False, eval_packages=eval_packages)
|
||||||
|
print(f"Saving {model_name}")
|
||||||
|
model.save()
|
||||||
59
epdb/management/commands/dump_enviformer.py
Normal file
59
epdb/management/commands/dump_enviformer.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tarfile
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.models import EnviFormer
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"model",
|
||||||
|
type=str,
|
||||||
|
help="Model UUID of the Model to Dump",
|
||||||
|
)
|
||||||
|
parser.add_argument("--output", type=str)
|
||||||
|
|
||||||
|
def package_dict_and_folder(self, dict_data, folder_path, output_path):
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
dict_filename = os.path.join(tmpdir, "data.json")
|
||||||
|
|
||||||
|
with open(dict_filename, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(dict_data, f, indent=2)
|
||||||
|
|
||||||
|
with tarfile.open(output_path, "w:gz") as tar:
|
||||||
|
tar.add(dict_filename, arcname="data.json")
|
||||||
|
tar.add(folder_path, arcname=os.path.basename(folder_path))
|
||||||
|
|
||||||
|
os.remove(dict_filename)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
output = options["output"]
|
||||||
|
|
||||||
|
if os.path.exists(output):
|
||||||
|
raise ValueError(f"Output file {output} already exists")
|
||||||
|
|
||||||
|
model = EnviFormer.objects.get(uuid=options["model"])
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"uuid": str(model.uuid),
|
||||||
|
"name": model.name,
|
||||||
|
"description": model.description,
|
||||||
|
"kv": model.kv,
|
||||||
|
"data_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
|
||||||
|
"eval_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
|
||||||
|
"threshold": model.threshold,
|
||||||
|
"eval_results": model.eval_results,
|
||||||
|
"multigen_eval": model.multigen_eval,
|
||||||
|
"model_status": model.model_status,
|
||||||
|
}
|
||||||
|
|
||||||
|
model_folder = os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid))
|
||||||
|
|
||||||
|
self.package_dict_and_folder(data, model_folder, output)
|
||||||
58
epdb/management/commands/import_external_identifiers.py
Normal file
58
epdb/management/commands/import_external_identifiers.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from csv import DictReader
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.models import Compound, CompoundStructure, Reaction, ExternalDatabase, ExternalIdentifier
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
STR_TO_MODEL = {
|
||||||
|
"Compound": Compound,
|
||||||
|
"CompoundStructure": CompoundStructure,
|
||||||
|
"Reaction": Reaction,
|
||||||
|
}
|
||||||
|
|
||||||
|
STR_TO_DATABASE = {
|
||||||
|
"ChEBI": ExternalDatabase.objects.get(name="ChEBI"),
|
||||||
|
"RHEA": ExternalDatabase.objects.get(name="RHEA"),
|
||||||
|
"KEGG Reaction": ExternalDatabase.objects.get(name="KEGG Reaction"),
|
||||||
|
"PubChem Compound": ExternalDatabase.objects.get(name="PubChem Compound"),
|
||||||
|
"PubChem Substance": ExternalDatabase.objects.get(name="PubChem Substance"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--data",
|
||||||
|
type=str,
|
||||||
|
help="Path of the ID Mapping file.",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--replace-host",
|
||||||
|
type=str,
|
||||||
|
help="Replace https://envipath.org/ with this host, e.g. http://localhost:8000/",
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
with open(options["data"]) as fh:
|
||||||
|
reader = DictReader(fh)
|
||||||
|
for row in reader:
|
||||||
|
clz = self.STR_TO_MODEL[row["model"]]
|
||||||
|
|
||||||
|
url = row["url"]
|
||||||
|
if options["replace_host"]:
|
||||||
|
url = url.replace("https://envipath.org/", options["replace_host"])
|
||||||
|
|
||||||
|
instance = clz.objects.get(url=url)
|
||||||
|
db = self.STR_TO_DATABASE[row["identifier_type"]]
|
||||||
|
|
||||||
|
ExternalIdentifier.objects.create(
|
||||||
|
content_object=instance,
|
||||||
|
database=db,
|
||||||
|
identifier_value=row["identifier_value"],
|
||||||
|
url=db.url_pattern.format(id=row["identifier_value"]),
|
||||||
|
is_primary=False,
|
||||||
|
)
|
||||||
29
epdb/management/commands/import_legacy_package.py
Normal file
29
epdb/management/commands/import_legacy_package.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.logic import PackageManager
|
||||||
|
from epdb.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--data",
|
||||||
|
type=str,
|
||||||
|
help="Path of the Package to import.",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--owner",
|
||||||
|
type=str,
|
||||||
|
help="Username of the desired Owner.",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
owner = User.objects.get(username=options["owner"])
|
||||||
|
package_data = json.load(open(options["data"]))
|
||||||
|
PackageManager.import_legacy_package(package_data, owner)
|
||||||
83
epdb/management/commands/load_enviformer.py
Normal file
83
epdb/management/commands/load_enviformer.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tarfile
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.models import EnviFormer
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"input",
|
||||||
|
type=str,
|
||||||
|
help=".tar.gz file containing the Model dump.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"package",
|
||||||
|
type=str,
|
||||||
|
help="Package UUID where the Model should be loaded to.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def read_dict_and_folder_from_archive(self, archive_path, extract_to="extracted_folder"):
|
||||||
|
with tarfile.open(archive_path, "r:gz") as tar:
|
||||||
|
tar.extractall(extract_to)
|
||||||
|
|
||||||
|
dict_path = os.path.join(extract_to, "data.json")
|
||||||
|
|
||||||
|
if not os.path.exists(dict_path):
|
||||||
|
raise FileNotFoundError("data.json not found in the archive.")
|
||||||
|
|
||||||
|
with open(dict_path, "r", encoding="utf-8") as f:
|
||||||
|
data_dict = json.load(f)
|
||||||
|
|
||||||
|
extracted_items = os.listdir(extract_to)
|
||||||
|
folders = [item for item in extracted_items if item != "data.json"]
|
||||||
|
folder_path = os.path.join(extract_to, folders[0]) if folders else None
|
||||||
|
|
||||||
|
return data_dict, folder_path
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
if not os.path.exists(options["input"]):
|
||||||
|
raise ValueError(f"Input file {options['input']} does not exist.")
|
||||||
|
|
||||||
|
target_package = Package.objects.get(uuid=options["package"])
|
||||||
|
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
data, folder = self.read_dict_and_folder_from_archive(options["input"], tmpdir)
|
||||||
|
|
||||||
|
model = EnviFormer()
|
||||||
|
model.package = target_package
|
||||||
|
# model.uuid = data["uuid"]
|
||||||
|
model.name = data["name"]
|
||||||
|
model.description = data["description"]
|
||||||
|
model.kv = data["kv"]
|
||||||
|
model.threshold = float(data["threshold"])
|
||||||
|
model.eval_results = data["eval_results"]
|
||||||
|
model.multigen_eval = data["multigen_eval"]
|
||||||
|
model.model_status = data["model_status"]
|
||||||
|
model.save()
|
||||||
|
|
||||||
|
for p_uuid in data["data_packages_uuids"]:
|
||||||
|
p = Package.objects.get(uuid=p_uuid)
|
||||||
|
model.data_packages.add(p)
|
||||||
|
|
||||||
|
for p_uuid in data["eval_packages_uuids"]:
|
||||||
|
p = Package.objects.get(uuid=p_uuid)
|
||||||
|
model.eval_packages.add(p)
|
||||||
|
|
||||||
|
target_folder = os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid))
|
||||||
|
|
||||||
|
shutil.copytree(folder, target_folder)
|
||||||
|
os.rename(
|
||||||
|
os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid), f"{data['uuid']}.ckpt"),
|
||||||
|
os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid), f"{model.uuid}.ckpt"),
|
||||||
|
)
|
||||||
67
epdb/management/commands/localize_urls.py
Normal file
67
epdb/management/commands/localize_urls.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
from django.apps import apps
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db.models import F, JSONField, TextField, Value
|
||||||
|
from django.db.models.functions import Cast, Replace
|
||||||
|
|
||||||
|
from epdb.models import EnviPathModel
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--old",
|
||||||
|
type=str,
|
||||||
|
help="Old Host, most likely https://envipath.org/",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--new",
|
||||||
|
type=str,
|
||||||
|
help="New Host, most likely http://localhost:8000/",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
print("Localizing urls for Package")
|
||||||
|
Package.objects.update(url=Replace(F("url"), Value(options["old"]), Value(options["new"])))
|
||||||
|
|
||||||
|
MODELS = [
|
||||||
|
"User",
|
||||||
|
"Group",
|
||||||
|
"Compound",
|
||||||
|
"CompoundStructure",
|
||||||
|
"Pathway",
|
||||||
|
"Edge",
|
||||||
|
"Node",
|
||||||
|
"Reaction",
|
||||||
|
"SimpleAmbitRule",
|
||||||
|
"SimpleRDKitRule",
|
||||||
|
"ParallelRule",
|
||||||
|
"SequentialRule",
|
||||||
|
"Scenario",
|
||||||
|
"Setting",
|
||||||
|
"MLRelativeReasoning",
|
||||||
|
"RuleBasedRelativeReasoning",
|
||||||
|
"EnviFormer",
|
||||||
|
"ApplicabilityDomain",
|
||||||
|
"EnzymeLink",
|
||||||
|
]
|
||||||
|
for model in MODELS:
|
||||||
|
obj_cls = apps.get_model("epdb", model)
|
||||||
|
print(f"Localizing urls for {model}")
|
||||||
|
obj_cls.objects.update(
|
||||||
|
url=Replace(F("url"), Value(options["old"]), Value(options["new"]))
|
||||||
|
)
|
||||||
|
if issubclass(obj_cls, EnviPathModel):
|
||||||
|
obj_cls.objects.update(
|
||||||
|
kv=Cast(
|
||||||
|
Replace(
|
||||||
|
Cast(F("kv"), output_field=TextField()),
|
||||||
|
Value(options["old"]),
|
||||||
|
Value(options["new"]),
|
||||||
|
),
|
||||||
|
output_field=JSONField(),
|
||||||
|
)
|
||||||
|
)
|
||||||
38
epdb/management/commands/update_job_logs.py
Normal file
38
epdb/management/commands/update_job_logs.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.models import JobLog
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--cleanup",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="Remove all logs older than this number of days. Default is None, which does not remove any logs.",
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
if options["cleanup"] is not None:
|
||||||
|
cleanup_dt = date.today() - timedelta(days=options["cleanup"])
|
||||||
|
print(JobLog.objects.filter(created__lt=cleanup_dt).delete())
|
||||||
|
|
||||||
|
logs = JobLog.objects.filter(status="INITIAL")
|
||||||
|
print(f"Found {logs.count()} logs to update")
|
||||||
|
updated = 0
|
||||||
|
for log in logs:
|
||||||
|
res = log.check_for_update()
|
||||||
|
if res:
|
||||||
|
updated += 1
|
||||||
|
|
||||||
|
print(f"Updated {updated} logs")
|
||||||
|
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
qs = JobLog.objects.values("status").annotate(total=Count("status"))
|
||||||
|
for r in qs:
|
||||||
|
print(r["status"], r["total"])
|
||||||
0
epdb/middleware/__init__.py
Normal file
0
epdb/middleware/__init__.py
Normal file
27
epdb/middleware/login_required_middleware.py
Normal file
27
epdb/middleware/login_required_middleware.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequiredMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
self.exempt_urls = [
|
||||||
|
reverse("login"),
|
||||||
|
reverse("logout"),
|
||||||
|
reverse("admin:login"),
|
||||||
|
reverse("admin:index"),
|
||||||
|
] + getattr(settings, "LOGIN_EXEMPT_URLS", [])
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
path = request.path_info
|
||||||
|
if not any(path.startswith(url) for url in self.exempt_urls):
|
||||||
|
if request.method == "GET":
|
||||||
|
if request.get_full_path() and request.get_full_path() != "/":
|
||||||
|
return redirect(
|
||||||
|
f"{settings.LOGIN_URL}?next={quote(request.get_full_path())}"
|
||||||
|
)
|
||||||
|
return redirect(settings.LOGIN_URL)
|
||||||
|
return self.get_response(request)
|
||||||
594
epdb/migrations/0001_initial.py
Normal file
594
epdb/migrations/0001_initial.py
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-07-22 20:58
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Compound',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EPModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Permission',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='License',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('link', models.URLField(verbose_name='link')),
|
||||||
|
('image_link', models.URLField(verbose_name='Image link')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rule',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='User',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
|
('email', models.EmailField(max_length=254, unique=True)),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'user',
|
||||||
|
'verbose_name_plural': 'users',
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='APIToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('hashed_key', models.CharField(max_length=128, unique=True)),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('expires_at', models.DateTimeField(blank=True, default=datetime.datetime(2025, 10, 20, 20, 58, 48, 351675, tzinfo=datetime.timezone.utc), null=True)),
|
||||||
|
('name', models.CharField(blank=True, help_text='Optional name for the token', max_length=100)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CompoundStructure',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('smiles', models.TextField(verbose_name='SMILES')),
|
||||||
|
('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')),
|
||||||
|
('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')),
|
||||||
|
('normalized_structure', models.BooleanField(default=False)),
|
||||||
|
('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='default_structure',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Edge',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EnviFormer',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PluginModel',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RuleBaseRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Group',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(verbose_name='Group name')),
|
||||||
|
('public', models.BooleanField(default=False, verbose_name='Public Group')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('group_member', models.ManyToManyField(related_name='groups_in_group', to='epdb.group', verbose_name='Group member')),
|
||||||
|
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')),
|
||||||
|
('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_group',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Node',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('depth', models.IntegerField(verbose_name='Node depth')),
|
||||||
|
('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')),
|
||||||
|
('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')),
|
||||||
|
('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='end_nodes',
|
||||||
|
field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='start_nodes',
|
||||||
|
field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Package',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')),
|
||||||
|
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='epmodel',
|
||||||
|
name='package',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='package',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_package',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.package', verbose_name='Default Package'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SequentialRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='package',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='polymorphic_ctype',
|
||||||
|
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Pathway',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='pathway',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='pathway',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Reaction',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
|
||||||
|
('multi_step', models.BooleanField(verbose_name='Multistep Reaction')),
|
||||||
|
('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')),
|
||||||
|
('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
||||||
|
('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')),
|
||||||
|
('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='edge_label',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Scenario',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('scenario_date', models.CharField(default='No date', max_length=256)),
|
||||||
|
('scenario_type', models.CharField(default='Not specified', max_length=256)),
|
||||||
|
('additional_information', models.JSONField(verbose_name='Additional Information')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
|
||||||
|
('parent', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.scenario')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='scenarios',
|
||||||
|
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Setting',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('public', models.BooleanField(default=False)),
|
||||||
|
('global_default', models.BooleanField(default=False)),
|
||||||
|
('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')),
|
||||||
|
('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')),
|
||||||
|
('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')),
|
||||||
|
('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')),
|
||||||
|
('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to='epdb.package', verbose_name='Setting Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='setting',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_setting',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MLRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
|
||||||
|
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('data_packages', models.ManyToManyField(related_name='data_packages', to='epdb.package', verbose_name='Data Packages')),
|
||||||
|
('eval_packages', models.ManyToManyField(related_name='eval_packages', to='epdb.package', verbose_name='Evaluation Packages')),
|
||||||
|
('rule_packages', models.ManyToManyField(related_name='rule_packages', to='epdb.package', verbose_name='Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicabilityDomain',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
|
||||||
|
('name', models.TextField(default='no name', verbose_name='Name')),
|
||||||
|
('description', models.TextField(default='no description', verbose_name='Descriptions')),
|
||||||
|
('kv', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('num_neighbours', models.FloatField(default=5)),
|
||||||
|
('reliability_threshold', models.FloatField(default=0.5)),
|
||||||
|
('local_compatibilty_threshold', models.FloatField(default=0.5)),
|
||||||
|
('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleAmbitRule',
|
||||||
|
fields=[
|
||||||
|
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
||||||
|
('smirks', models.TextField(verbose_name='SMIRKS')),
|
||||||
|
('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')),
|
||||||
|
('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.simplerule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleRDKitRule',
|
||||||
|
fields=[
|
||||||
|
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
|
||||||
|
('reaction_smarts', models.TextField(verbose_name='SMIRKS')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.simplerule',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SequentialRuleOrdering',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('order_index', models.IntegerField()),
|
||||||
|
('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')),
|
||||||
|
('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sequentialrule',
|
||||||
|
name='simple_rules',
|
||||||
|
field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ParallelRule',
|
||||||
|
fields=[
|
||||||
|
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
|
||||||
|
('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('epdb.rule',),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='compound',
|
||||||
|
unique_together={('uuid', 'package')},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GroupPackagePermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('package', 'group')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserPackagePermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('package', 'user')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserSettingPermission',
|
||||||
|
fields=[
|
||||||
|
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
|
||||||
|
('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('setting', 'user')},
|
||||||
|
},
|
||||||
|
bases=('epdb.permission',),
|
||||||
|
),
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,128 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-25 18:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('epdb', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalDatabase',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')),
|
||||||
|
('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||||
|
('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')),
|
||||||
|
('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'External Database',
|
||||||
|
'verbose_name_plural': 'External Databases',
|
||||||
|
'db_table': 'epdb_external_database',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='apitoken',
|
||||||
|
options={'ordering': ['-created'], 'verbose_name': 'API Token', 'verbose_name_plural': 'API Tokens'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='edge',
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='edge',
|
||||||
|
name='polymorphic_ctype',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='is_active',
|
||||||
|
field=models.BooleanField(default=True, help_text='Whether this token is active'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='modified',
|
||||||
|
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='functional_groups',
|
||||||
|
field=models.JSONField(blank=True, default=dict, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='app_domain',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='created',
|
||||||
|
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='expires_at',
|
||||||
|
field=models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='hashed_key',
|
||||||
|
field=models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(help_text='Descriptive name for this token', max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='apitoken',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='num_neighbours',
|
||||||
|
field=models.IntegerField(default=5),
|
||||||
|
),
|
||||||
|
migrations.AlterModelTable(
|
||||||
|
name='apitoken',
|
||||||
|
table='epdb_api_token',
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalIdentifier',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('object_id', models.IntegerField()),
|
||||||
|
('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')),
|
||||||
|
('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')),
|
||||||
|
('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')),
|
||||||
|
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||||
|
('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'External Identifier',
|
||||||
|
'verbose_name_plural': 'External Identifiers',
|
||||||
|
'db_table': 'epdb_external_identifier',
|
||||||
|
'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')],
|
||||||
|
'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,228 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-26 17:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def populate_url(apps, schema_editor):
|
||||||
|
MODELS = [
|
||||||
|
'User',
|
||||||
|
'Group',
|
||||||
|
'Package',
|
||||||
|
'Compound',
|
||||||
|
'CompoundStructure',
|
||||||
|
'Pathway',
|
||||||
|
'Edge',
|
||||||
|
'Node',
|
||||||
|
'Reaction',
|
||||||
|
'SimpleAmbitRule',
|
||||||
|
'SimpleRDKitRule',
|
||||||
|
'ParallelRule',
|
||||||
|
'SequentialRule',
|
||||||
|
'Scenario',
|
||||||
|
'Setting',
|
||||||
|
'MLRelativeReasoning',
|
||||||
|
'EnviFormer',
|
||||||
|
'ApplicabilityDomain',
|
||||||
|
]
|
||||||
|
for model in MODELS:
|
||||||
|
obj_cls = apps.get_model("epdb", model)
|
||||||
|
for obj in obj_cls.objects.all():
|
||||||
|
obj.url = assemble_url(obj)
|
||||||
|
if obj.url is None:
|
||||||
|
raise ValueError(f"Could not assemble url for {obj}")
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
def assemble_url(obj):
|
||||||
|
from django.conf import settings as s
|
||||||
|
match obj.__class__.__name__:
|
||||||
|
case 'User':
|
||||||
|
return '{}/user/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case 'Group':
|
||||||
|
return '{}/group/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case 'Package':
|
||||||
|
return '{}/package/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case 'Compound':
|
||||||
|
return '{}/compound/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'CompoundStructure':
|
||||||
|
return '{}/structure/{}'.format(obj.compound.url, obj.uuid)
|
||||||
|
case 'SimpleAmbitRule':
|
||||||
|
return '{}/simple-ambit-rule/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'SimpleRDKitRule':
|
||||||
|
return '{}/simple-rdkit-rule/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'ParallelRule':
|
||||||
|
return '{}/parallel-rule/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'SequentialRule':
|
||||||
|
return '{}/sequential-rule/{}'.format(obj.compound.url, obj.uuid)
|
||||||
|
case 'Reaction':
|
||||||
|
return '{}/reaction/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'Pathway':
|
||||||
|
return '{}/pathway/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'Node':
|
||||||
|
return '{}/node/{}'.format(obj.pathway.url, obj.uuid)
|
||||||
|
case 'Edge':
|
||||||
|
return '{}/edge/{}'.format(obj.pathway.url, obj.uuid)
|
||||||
|
case 'MLRelativeReasoning':
|
||||||
|
return '{}/model/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'EnviFormer':
|
||||||
|
return '{}/model/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'ApplicabilityDomain':
|
||||||
|
return '{}/model/{}/applicability-domain/{}'.format(obj.model.package.url, obj.model.uuid, obj.uuid)
|
||||||
|
case 'Scenario':
|
||||||
|
return '{}/scenario/{}'.format(obj.package.url, obj.uuid)
|
||||||
|
case 'Setting':
|
||||||
|
return '{}/setting/{}'.format(s.SERVER_URL, obj.uuid)
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown model {obj.__class__.__name__}")
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0002_externaldatabase_alter_apitoken_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compound',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='edge',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='epmodel',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='group',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='node',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='package',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='scenario',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='setting',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=False, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.RunPython(populate_url, reverse_code=migrations.RunPython.noop),
|
||||||
|
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='applicabilitydomain',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='compound',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='compoundstructure',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='edge',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='epmodel',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='group',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='node',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='package',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='pathway',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='reaction',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rule',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='scenario',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='setting',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='url',
|
||||||
|
field=models.TextField(null=True, unique=True, verbose_name='URL'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-09 09:21
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0001_squashed_0003_applicabilitydomain_url_compound_url_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='mlrelativereasoning',
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='data_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='eval_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='rule_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RuleBasedRelativeReasoning',
|
||||||
|
fields=[
|
||||||
|
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
|
||||||
|
('threshold', models.FloatField(default=0.5)),
|
||||||
|
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
|
||||||
|
('min_count', models.IntegerField(default=10)),
|
||||||
|
('max_count', models.IntegerField(default=0)),
|
||||||
|
('app_domain', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain')),
|
||||||
|
('data_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages')),
|
||||||
|
('eval_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages')),
|
||||||
|
('rule_packages', models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('epdb.epmodel',),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='RuleBaseRelativeReasoning',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
epdb/migrations/0005_alter_group_group_member.py
Normal file
18
epdb/migrations/0005_alter_group_group_member.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-11 06:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0004_alter_mlrelativereasoning_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='group',
|
||||||
|
name='group_member',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='groups_in_group', to='epdb.group', verbose_name='Group member'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-09-18 06:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0005_alter_group_group_member'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mlrelativereasoning',
|
||||||
|
name='multigen_eval',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rulebasedrelativereasoning',
|
||||||
|
name='multigen_eval',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-10-07 08:19
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epdb', '0006_mlrelativereasoning_multigen_eval_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='enviformer',
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='app_domain',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='data_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_data_packages', to='epdb.package', verbose_name='Data Packages'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='eval_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_eval_packages', to='epdb.package', verbose_name='Evaluation Packages'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='eval_results',
|
||||||
|
field=models.JSONField(blank=True, default=dict, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='model_status',
|
||||||
|
field=models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='multigen_eval',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='enviformer',
|
||||||
|
name='rule_packages',
|
||||||
|
field=models.ManyToManyField(related_name='%(app_label)s_%(class)s_rule_packages', to='epdb.package', verbose_name='Rule Packages'),
|
||||||
|
),
|
||||||
|
]
|
||||||
64
epdb/migrations/0008_enzymelink.py
Normal file
64
epdb/migrations/0008_enzymelink.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-10 06:58
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0007_alter_enviformer_options_enviformer_app_domain_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="EnzymeLink",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
model_utils.fields.AutoCreatedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"uuid",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4, unique=True, verbose_name="UUID of this object"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.TextField(default="no name", verbose_name="Name")),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.TextField(default="no description", verbose_name="Descriptions"),
|
||||||
|
),
|
||||||
|
("url", models.TextField(null=True, unique=True, verbose_name="URL")),
|
||||||
|
("kv", models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
("ec_number", models.TextField(verbose_name="EC Number")),
|
||||||
|
("classification_level", models.IntegerField(verbose_name="Classification Level")),
|
||||||
|
("linking_method", models.TextField(verbose_name="Linking Method")),
|
||||||
|
("edge_evidence", models.ManyToManyField(to="epdb.edge")),
|
||||||
|
("reaction_evidence", models.ManyToManyField(to="epdb.reaction")),
|
||||||
|
(
|
||||||
|
"rule",
|
||||||
|
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="epdb.rule"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
66
epdb/migrations/0009_joblog.py
Normal file
66
epdb/migrations/0009_joblog.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-27 09:39
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0008_enzymelink"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="JobLog",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
model_utils.fields.AutoCreatedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("task_id", models.UUIDField(unique=True)),
|
||||||
|
("job_name", models.TextField()),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("INITIAL", "Initial"),
|
||||||
|
("SUCCESS", "Success"),
|
||||||
|
("FAILURE", "Failure"),
|
||||||
|
("REVOKED", "Revoked"),
|
||||||
|
("IGNORED", "Ignored"),
|
||||||
|
],
|
||||||
|
default="INITIAL",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("done_at", models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
("task_result", models.TextField(blank=True, default=None, null=True)),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,190 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-29 13:32
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0009_joblog"),
|
||||||
|
("public", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userpackagepermission",
|
||||||
|
name="package",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Permission on",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="grouppackagepermission",
|
||||||
|
name="package",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Permission on",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="epmodel",
|
||||||
|
name="package",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Package",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rule",
|
||||||
|
name="package",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Package",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compound",
|
||||||
|
name="package",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Package",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="scenario",
|
||||||
|
name="package",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Package",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="pathway",
|
||||||
|
name="package",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Package",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reaction",
|
||||||
|
name="package",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Package",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="user",
|
||||||
|
name="default_package",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Default Package",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="enviformer",
|
||||||
|
name="data_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="%(app_label)s_%(class)s_data_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Data Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="enviformer",
|
||||||
|
name="eval_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Evaluation Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="enviformer",
|
||||||
|
name="rule_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Rule Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="mlrelativereasoning",
|
||||||
|
name="data_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="%(app_label)s_%(class)s_data_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Data Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="mlrelativereasoning",
|
||||||
|
name="eval_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Evaluation Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="mlrelativereasoning",
|
||||||
|
name="rule_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Rule Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rulebasedrelativereasoning",
|
||||||
|
name="data_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="%(app_label)s_%(class)s_data_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Data Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rulebasedrelativereasoning",
|
||||||
|
name="eval_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Evaluation Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rulebasedrelativereasoning",
|
||||||
|
name="rule_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Rule Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="setting",
|
||||||
|
name="rule_packages",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="setting_rule_packages",
|
||||||
|
to="public.package",
|
||||||
|
verbose_name="Setting Rule Packages",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="Package",
|
||||||
|
),
|
||||||
|
]
|
||||||
0
epdb/migrations/__init__.py
Normal file
0
epdb/migrations/__init__.py
Normal file
3733
epdb/models.py
Normal file
3733
epdb/models.py
Normal file
File diff suppressed because it is too large
Load Diff
37
epdb/signals.py
Normal file
37
epdb/signals.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db.models.signals import pre_delete, post_delete
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from epdb.models import Node, Edge, EPModel
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_delete, sender=Node)
|
||||||
|
@transaction.atomic
|
||||||
|
def delete_orphan_edges(sender, instance, **kwargs):
|
||||||
|
# check if the node that is about to be deleted is the only start node
|
||||||
|
for edge in Edge.objects.filter(start_nodes=instance):
|
||||||
|
if edge.start_nodes.count() == 1:
|
||||||
|
edge.delete()
|
||||||
|
|
||||||
|
# same for end_nodes
|
||||||
|
for edge in Edge.objects.filter(end_nodes=instance):
|
||||||
|
# check if the node that is about to be deleted is the only start node
|
||||||
|
if edge.end_nodes.count() == 1:
|
||||||
|
edge.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=EPModel)
|
||||||
|
def delete_epmodel_files(sender, instance, **kwargs):
|
||||||
|
# Delete the files on disk for the deleted model
|
||||||
|
mod_uuid = str(instance.uuid)
|
||||||
|
|
||||||
|
for f in os.listdir(s.MODEL_DIR):
|
||||||
|
if f.startswith(mod_uuid):
|
||||||
|
logger.info(f"Deleting {os.path.join(s.MODEL_DIR, f)}")
|
||||||
|
os.remove(os.path.join(s.MODEL_DIR, f))
|
||||||
286
epdb/tasks.py
Normal file
286
epdb/tasks.py
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
import csv
|
||||||
|
import io
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Any, Callable, List, Optional
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from celery import shared_task
|
||||||
|
from celery.utils.functional import LRUCache
|
||||||
|
from django.conf import settings as s
|
||||||
|
|
||||||
|
from epdb.logic import SPathway
|
||||||
|
from epdb.models import Edge, EPModel, JobLog, Node, Pathway, Rule, Setting, User
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
||||||
|
|
||||||
|
|
||||||
|
def get_ml_model(model_pk: int):
|
||||||
|
if model_pk not in ML_CACHE:
|
||||||
|
ML_CACHE[model_pk] = EPModel.objects.get(id=model_pk)
|
||||||
|
return ML_CACHE[model_pk]
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_eager(user: "User", job: Callable, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
x = job(*args, **kwargs)
|
||||||
|
log = JobLog()
|
||||||
|
log.user = user
|
||||||
|
log.task_id = uuid4()
|
||||||
|
log.job_name = job.__name__
|
||||||
|
log.status = "SUCCESS"
|
||||||
|
log.done_at = datetime.now()
|
||||||
|
log.task_result = str(x) if x else None
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
return x
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch(user: "User", job: Callable, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
x = job.delay(*args, **kwargs)
|
||||||
|
log = JobLog()
|
||||||
|
log.user = user
|
||||||
|
log.task_id = x.task_id
|
||||||
|
log.job_name = job.__name__
|
||||||
|
log.status = "INITIAL"
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
return x.result
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue="background")
|
||||||
|
def mul(a, b):
|
||||||
|
return a * b
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue="predict")
|
||||||
|
def predict_simple(model_pk: int, smiles: str):
|
||||||
|
mod = get_ml_model(model_pk)
|
||||||
|
res = mod.predict(smiles)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue="background")
|
||||||
|
def send_registration_mail(user_pk: int):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(bind=True, queue="model")
|
||||||
|
def build_model(self, model_pk: int):
|
||||||
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=mod.url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
mod.build_dataset()
|
||||||
|
mod.build_model()
|
||||||
|
except Exception as e:
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(
|
||||||
|
status="FAILED", task_result=mod.url
|
||||||
|
)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=mod.url)
|
||||||
|
|
||||||
|
return mod.url
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(bind=True, queue="model")
|
||||||
|
def evaluate_model(self, model_pk: int, multigen: bool, package_pks: Optional[list] = None):
|
||||||
|
packages = None
|
||||||
|
|
||||||
|
if package_pks:
|
||||||
|
packages = Package.objects.filter(pk__in=package_pks)
|
||||||
|
|
||||||
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=mod.url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
mod.evaluate_model(multigen, eval_packages=packages)
|
||||||
|
except Exception as e:
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(
|
||||||
|
status="FAILED", task_result=mod.url
|
||||||
|
)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=mod.url)
|
||||||
|
|
||||||
|
return mod.url
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(queue="model")
|
||||||
|
def retrain(model_pk: int):
|
||||||
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
|
mod.retrain()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(bind=True, queue="predict")
|
||||||
|
def predict(
|
||||||
|
self,
|
||||||
|
pw_pk: int,
|
||||||
|
pred_setting_pk: int,
|
||||||
|
limit: Optional[int] = None,
|
||||||
|
node_pk: Optional[int] = None,
|
||||||
|
) -> Pathway:
|
||||||
|
pw = Pathway.objects.get(id=pw_pk)
|
||||||
|
setting = Setting.objects.get(id=pred_setting_pk)
|
||||||
|
# If the setting has a model add/restore it from the cache
|
||||||
|
if setting.model is not None:
|
||||||
|
setting.model = get_ml_model(setting.model.pk)
|
||||||
|
|
||||||
|
pw.kv.update(**{"status": "running"})
|
||||||
|
pw.save()
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=pw.url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# regular prediction
|
||||||
|
if limit is not None:
|
||||||
|
spw = SPathway(prediction_setting=setting, persist=pw)
|
||||||
|
level = 0
|
||||||
|
while not spw.done:
|
||||||
|
spw.predict_step(from_depth=level)
|
||||||
|
level += 1
|
||||||
|
|
||||||
|
# break in case we are in incremental mode
|
||||||
|
if limit != -1:
|
||||||
|
if level >= limit:
|
||||||
|
break
|
||||||
|
|
||||||
|
elif node_pk is not None:
|
||||||
|
n = Node.objects.get(id=node_pk, pathway=pw)
|
||||||
|
spw = SPathway.from_pathway(pw)
|
||||||
|
spw.predict_step(from_node=n)
|
||||||
|
else:
|
||||||
|
raise ValueError("Neither limit nor node_pk given!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
pw.kv.update({"status": "failed"})
|
||||||
|
pw.save()
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(
|
||||||
|
status="FAILED", task_result=pw.url
|
||||||
|
)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
|
pw.kv.update(**{"status": "completed"})
|
||||||
|
pw.save()
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=pw.url)
|
||||||
|
|
||||||
|
return pw.url
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(bind=True, queue="background")
|
||||||
|
def identify_missing_rules(
|
||||||
|
self,
|
||||||
|
pw_pks: List[int],
|
||||||
|
rule_package_pk: int,
|
||||||
|
):
|
||||||
|
from utilities.misc import PathwayUtils
|
||||||
|
|
||||||
|
rules = Package.objects.get(pk=rule_package_pk).get_applicable_rules()
|
||||||
|
|
||||||
|
rows: List[Any] = []
|
||||||
|
header = [
|
||||||
|
"Package Name",
|
||||||
|
"Pathway Name",
|
||||||
|
"Educt Name",
|
||||||
|
"Educt SMILES",
|
||||||
|
"Reaction Name",
|
||||||
|
"Reaction SMIRKS",
|
||||||
|
"Triggered Rules",
|
||||||
|
"Reactant SMARTS",
|
||||||
|
"Product SMARTS",
|
||||||
|
"Product Names",
|
||||||
|
"Product SMILES",
|
||||||
|
]
|
||||||
|
|
||||||
|
rows.append(header)
|
||||||
|
|
||||||
|
for pw in Pathway.objects.filter(pk__in=pw_pks):
|
||||||
|
pu = PathwayUtils(pw)
|
||||||
|
|
||||||
|
missing_rules = pu.find_missing_rules(rules)
|
||||||
|
|
||||||
|
package_name = pw.package.name
|
||||||
|
pathway_name = pw.name
|
||||||
|
|
||||||
|
for edge_url, rule_chain in missing_rules.items():
|
||||||
|
row: List[Any] = [package_name, pathway_name]
|
||||||
|
edge = Edge.objects.get(url=edge_url)
|
||||||
|
educts = edge.start_nodes.all()
|
||||||
|
|
||||||
|
for educt in educts:
|
||||||
|
row.append(educt.default_node_label.name)
|
||||||
|
row.append(educt.default_node_label.smiles)
|
||||||
|
|
||||||
|
row.append(edge.edge_label.name)
|
||||||
|
row.append(edge.edge_label.smirks())
|
||||||
|
|
||||||
|
rule_names = []
|
||||||
|
reactant_smarts = []
|
||||||
|
product_smarts = []
|
||||||
|
|
||||||
|
for r in rule_chain:
|
||||||
|
r = Rule.objects.get(url=r[0])
|
||||||
|
rule_names.append(r.name)
|
||||||
|
|
||||||
|
rs = r.reactants_smarts
|
||||||
|
if isinstance(rs, set):
|
||||||
|
rs = list(rs)
|
||||||
|
|
||||||
|
ps = r.products_smarts
|
||||||
|
if isinstance(ps, set):
|
||||||
|
ps = list(ps)
|
||||||
|
|
||||||
|
reactant_smarts.append(rs)
|
||||||
|
product_smarts.append(ps)
|
||||||
|
|
||||||
|
row.append(rule_names)
|
||||||
|
row.append(reactant_smarts)
|
||||||
|
row.append(product_smarts)
|
||||||
|
|
||||||
|
products = edge.end_nodes.all()
|
||||||
|
product_names = []
|
||||||
|
product_smiles = []
|
||||||
|
|
||||||
|
for product in products:
|
||||||
|
product_names.append(product.default_node_label.name)
|
||||||
|
product_smiles.append(product.default_node_label.smiles)
|
||||||
|
|
||||||
|
row.append(product_names)
|
||||||
|
row.append(product_smiles)
|
||||||
|
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
buffer = io.StringIO()
|
||||||
|
|
||||||
|
writer = csv.writer(buffer)
|
||||||
|
writer.writerows(rows)
|
||||||
|
|
||||||
|
buffer.seek(0)
|
||||||
|
|
||||||
|
return buffer.getvalue()
|
||||||
0
epdb/templatetags/__init__.py
Normal file
0
epdb/templatetags/__init__.py
Normal file
21
epdb/templatetags/envipytags.py
Normal file
21
epdb/templatetags/envipytags.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django import template
|
||||||
|
from pydantic import AnyHttpUrl, ValidationError
|
||||||
|
from pydantic.type_adapter import TypeAdapter
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
url_adapter = TypeAdapter(AnyHttpUrl)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def classname(obj):
|
||||||
|
return obj.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def is_url(value):
|
||||||
|
try:
|
||||||
|
url_adapter.validate_python(value)
|
||||||
|
return True
|
||||||
|
except ValidationError:
|
||||||
|
return False
|
||||||
196
epdb/urls.py
Normal file
196
epdb/urls.py
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.urls import path, re_path
|
||||||
|
|
||||||
|
from . import views as v
|
||||||
|
|
||||||
|
UUID = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# Home
|
||||||
|
re_path(r"^$", v.index, name="index"),
|
||||||
|
# Login
|
||||||
|
re_path(r"^login", v.login, name="login"),
|
||||||
|
re_path(r"^logout", v.logout, name="logout"),
|
||||||
|
re_path(r"^register", v.register, name="register"),
|
||||||
|
# Built-In views
|
||||||
|
path(
|
||||||
|
"password_reset/",
|
||||||
|
auth_views.PasswordResetView.as_view(template_name="static/password_reset_form.html"),
|
||||||
|
name="password_reset",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"password_reset/done/",
|
||||||
|
auth_views.PasswordResetDoneView.as_view(template_name="static/password_reset_done.html"),
|
||||||
|
name="password_reset_done",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"reset/<uidb64>/<token>/",
|
||||||
|
auth_views.PasswordResetConfirmView.as_view(
|
||||||
|
template_name="static/password_reset_confirm.html"
|
||||||
|
),
|
||||||
|
name="password_reset_confirm",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"reset/done/",
|
||||||
|
auth_views.PasswordResetCompleteView.as_view(
|
||||||
|
template_name="static/password_reset_complete.html"
|
||||||
|
),
|
||||||
|
name="password_reset_complete",
|
||||||
|
),
|
||||||
|
# Top level urls
|
||||||
|
re_path(r"^package$", v.packages, name="packages"),
|
||||||
|
re_path(r"^compound$", v.compounds, name="compounds"),
|
||||||
|
re_path(r"^rule$", v.rules, name="rules"),
|
||||||
|
re_path(r"^reaction$", v.reactions, name="reactions"),
|
||||||
|
re_path(r"^pathway$", v.pathways, name="pathways"),
|
||||||
|
re_path(r"^scenario$", v.scenarios, name="scenarios"),
|
||||||
|
re_path(r"^model$", v.models, name="model"),
|
||||||
|
re_path(r"^user$", v.users, name="users"),
|
||||||
|
re_path(r"^group$", v.groups, name="groups"),
|
||||||
|
re_path(r"^search$", v.search, name="search"),
|
||||||
|
# User Detail
|
||||||
|
re_path(rf"^user/(?P<user_uuid>{UUID})", v.user, name="user"),
|
||||||
|
# Group Detail
|
||||||
|
re_path(rf"^group/(?P<group_uuid>{UUID})$", v.group, name="group detail"),
|
||||||
|
# "in package" urls
|
||||||
|
re_path(rf"^package/(?P<package_uuid>{UUID})$", v.package, name="package detail"),
|
||||||
|
# Compound
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/compound$",
|
||||||
|
v.package_compounds,
|
||||||
|
name="package compound list",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})$",
|
||||||
|
v.package_compound,
|
||||||
|
name="package compound detail",
|
||||||
|
),
|
||||||
|
# Compound Structure
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})/structure$",
|
||||||
|
v.package_compound_structures,
|
||||||
|
name="package compound structure list",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})/structure/(?P<structure_uuid>{UUID})$",
|
||||||
|
v.package_compound_structure,
|
||||||
|
name="package compound structure detail",
|
||||||
|
),
|
||||||
|
# Rule
|
||||||
|
re_path(rf"^package/(?P<package_uuid>{UUID})/rule$", v.package_rules, name="package rule list"),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})$",
|
||||||
|
v.package_rule,
|
||||||
|
name="package rule detail",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})$",
|
||||||
|
v.package_rule,
|
||||||
|
name="package rule detail",
|
||||||
|
),
|
||||||
|
# re_path(
|
||||||
|
# rf"^package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$",
|
||||||
|
# v.package_rule,
|
||||||
|
# name="package rule detail",
|
||||||
|
# ),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})$",
|
||||||
|
v.package_rule,
|
||||||
|
name="package rule detail",
|
||||||
|
),
|
||||||
|
# re_path(
|
||||||
|
# rf"^package/(?P<package_uuid>{UUID})/sequential-rule/(?P<rule_uuid>{UUID})$",
|
||||||
|
# v.package_rule,
|
||||||
|
# name="package rule detail",
|
||||||
|
# ),
|
||||||
|
# EnzymeLinks
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
|
||||||
|
v.package_rule_enzymelink,
|
||||||
|
name="package rule enzymelink detail",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
|
||||||
|
v.package_rule_enzymelink,
|
||||||
|
name="package rule enzymelink detail",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
|
||||||
|
v.package_rule_enzymelink,
|
||||||
|
name="package rule enzymelink detail",
|
||||||
|
),
|
||||||
|
# Reaction
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/reaction$",
|
||||||
|
v.package_reactions,
|
||||||
|
name="package reaction list",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/reaction/(?P<reaction_uuid>{UUID})$",
|
||||||
|
v.package_reaction,
|
||||||
|
name="package reaction detail",
|
||||||
|
),
|
||||||
|
# # Pathway
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/pathway$",
|
||||||
|
v.package_pathways,
|
||||||
|
name="package pathway list",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})$",
|
||||||
|
v.package_pathway,
|
||||||
|
name="package pathway detail",
|
||||||
|
),
|
||||||
|
# Pathway Nodes
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$",
|
||||||
|
v.package_pathway_nodes,
|
||||||
|
name="package pathway node list",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node/(?P<node_uuid>{UUID})$",
|
||||||
|
v.package_pathway_node,
|
||||||
|
name="package pathway node detail",
|
||||||
|
),
|
||||||
|
# Pathway Edges
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge$",
|
||||||
|
v.package_pathway_edges,
|
||||||
|
name="package pathway edge list",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge/(?P<edge_uuid>{UUID})$",
|
||||||
|
v.package_pathway_edge,
|
||||||
|
name="package pathway edge detail",
|
||||||
|
),
|
||||||
|
# Scenario
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/scenario$",
|
||||||
|
v.package_scenarios,
|
||||||
|
name="package scenario list",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/scenario/(?P<scenario_uuid>{UUID})$",
|
||||||
|
v.package_scenario,
|
||||||
|
name="package scenario detail",
|
||||||
|
),
|
||||||
|
# Model
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/model$", v.package_models, name="package model list"
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/model/(?P<model_uuid>{UUID})$",
|
||||||
|
v.package_model,
|
||||||
|
name="package model detail",
|
||||||
|
),
|
||||||
|
re_path(r"^setting$", v.settings, name="settings"),
|
||||||
|
re_path(rf"^setting/(?P<setting_uuid>{UUID})", v.setting, name="setting"),
|
||||||
|
re_path(r"^indigo/info$", v.indigo, name="indigo_info"),
|
||||||
|
re_path(r"^indigo/aromatize$", v.aromatize, name="indigo_aromatize"),
|
||||||
|
re_path(r"^indigo/dearomatize$", v.dearomatize, name="indigo_dearomatize"),
|
||||||
|
re_path(r"^indigo/layout$", v.layout, name="indigo_layout"),
|
||||||
|
re_path(r"^depict$", v.depict, name="depict"),
|
||||||
|
re_path(r"^jobs", v.jobs, name="jobs"),
|
||||||
|
# OAuth Stuff
|
||||||
|
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
|
||||||
|
]
|
||||||
2840
epdb/views.py
Normal file
2840
epdb/views.py
Normal file
File diff suppressed because it is too large
Load Diff
1
fixtures/Fixture_Package.json
Normal file
1
fixtures/Fixture_Package.json
Normal file
File diff suppressed because one or more lines are too long
BIN
fixtures/ambit_rules.json.gz
Normal file
BIN
fixtures/ambit_rules.json.gz
Normal file
Binary file not shown.
1
fixtures/migration_status_per_rule.json
Normal file
1
fixtures/migration_status_per_rule.json
Normal file
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.
File diff suppressed because it is too large
Load Diff
441171
fixtures/packages/2025-07-18/EAWAG-BBD.json
Normal file
441171
fixtures/packages/2025-07-18/EAWAG-BBD.json
Normal file
File diff suppressed because it is too large
Load Diff
284131
fixtures/packages/2025-07-18/EAWAG-SEDIMENT.json
Normal file
284131
fixtures/packages/2025-07-18/EAWAG-SEDIMENT.json
Normal file
File diff suppressed because it is too large
Load Diff
187048
fixtures/packages/2025-07-18/EAWAG-SLUDGE.json
Normal file
187048
fixtures/packages/2025-07-18/EAWAG-SLUDGE.json
Normal file
File diff suppressed because it is too large
Load Diff
1574452
fixtures/packages/2025-07-18/EAWAG-SOIL.json
Normal file
1574452
fixtures/packages/2025-07-18/EAWAG-SOIL.json
Normal file
File diff suppressed because one or more lines are too long
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.jsonl.gz
Normal file
BIN
fixtures/test_fixtures.jsonl.gz
Normal file
Binary file not shown.
BIN
fixtures/test_fixtures_incl_model.jsonl.gz
Normal file
BIN
fixtures/test_fixtures_incl_model.jsonl.gz
Normal file
Binary file not shown.
22
manage.py
Executable file
22
manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
0
migration/__init__.py
Normal file
0
migration/__init__.py
Normal file
3
migration/admin.py
Normal file
3
migration/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
migration/apps.py
Normal file
6
migration/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class MigrationConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'migration'
|
||||||
0
migration/migrations/__init__.py
Normal file
0
migration/migrations/__init__.py
Normal file
3
migration/models.py
Normal file
3
migration/models.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
migration/tests.py
Normal file
3
migration/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
16
migration/urls.py
Normal file
16
migration/urls.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
|
from . import views as v
|
||||||
|
|
||||||
|
UUID = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(rf'^migration$', v.migration, name='migration'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-rule/(?P<rule_uuid>{UUID})$',v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/sequential-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
|
||||||
|
re_path(rf'^migration/compare$', v.compare, name='compare'),
|
||||||
|
]
|
||||||
265
migration/views.py
Normal file
265
migration/views.py
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.http import HttpResponseNotAllowed
|
||||||
|
from django.shortcuts import render
|
||||||
|
from rdkit import Chem
|
||||||
|
from rdkit.Chem.MolStandardize import rdMolStandardize
|
||||||
|
|
||||||
|
from epdb.models import CompoundStructure, Rule, SimpleAmbitRule
|
||||||
|
from epdb.views import get_base_context
|
||||||
|
from utilities.chem import FormatConverter
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
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):
|
||||||
|
if request.method == "GET":
|
||||||
|
context = get_base_context(request)
|
||||||
|
|
||||||
|
if (
|
||||||
|
os.path.exists(s.BASE_DIR / "fixtures" / "migration_status_per_rule.json")
|
||||||
|
and request.GET.get("force") is None
|
||||||
|
):
|
||||||
|
migration_status = json.load(
|
||||||
|
open(s.BASE_DIR / "fixtures" / "migration_status_per_rule.json")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
BBD = Package.objects.get(
|
||||||
|
url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1"
|
||||||
|
)
|
||||||
|
ALL_SMILES = [
|
||||||
|
cs.smiles for cs in CompoundStructure.objects.filter(compound__package=BBD)
|
||||||
|
]
|
||||||
|
RULES = SimpleAmbitRule.objects.filter(package=BBD)
|
||||||
|
|
||||||
|
results = list()
|
||||||
|
num_rules = len(RULES)
|
||||||
|
success = 0
|
||||||
|
error = 0
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
for i, r in enumerate(RULES):
|
||||||
|
logger.debug(f"\r{i + 1:03d}/{num_rules}")
|
||||||
|
res = True
|
||||||
|
for smiles in ALL_SMILES:
|
||||||
|
try:
|
||||||
|
ambit_res, _, rdkit_res, _ = run_both_engines(smiles, r.smirks)
|
||||||
|
|
||||||
|
res &= set(ambit_res) == set(rdkit_res)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"name": r.name,
|
||||||
|
"detail_url": s.SERVER_URL
|
||||||
|
+ "/migration/"
|
||||||
|
+ r.url.replace("https://envipath.org/", "").replace(
|
||||||
|
"http://localhost:8000/", ""
|
||||||
|
),
|
||||||
|
"id": str(r.uuid),
|
||||||
|
"url": r.url,
|
||||||
|
"status": res,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if res:
|
||||||
|
success += 1
|
||||||
|
else:
|
||||||
|
error += 1
|
||||||
|
|
||||||
|
total += 1
|
||||||
|
results = sorted(results, key=lambda x: (x["status"], x["name"]))
|
||||||
|
|
||||||
|
migration_status = {
|
||||||
|
"results": results,
|
||||||
|
"success": success,
|
||||||
|
"error": error,
|
||||||
|
"total": total,
|
||||||
|
}
|
||||||
|
|
||||||
|
json.dump(
|
||||||
|
migration_status,
|
||||||
|
open(s.BASE_DIR / "fixtures" / "migration_status_per_rule.json", "w"),
|
||||||
|
)
|
||||||
|
|
||||||
|
for r in migration_status["results"]:
|
||||||
|
r["detail_url"] = r["detail_url"].replace("http://localhost:8000", s.SERVER_URL)
|
||||||
|
|
||||||
|
context.update(**migration_status)
|
||||||
|
|
||||||
|
return render(request, "migration.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def migration_detail(request, package_uuid, rule_uuid):
|
||||||
|
if request.method == "GET":
|
||||||
|
context = get_base_context(request)
|
||||||
|
|
||||||
|
BBD = Package.objects.get(name="EAWAG-BBD")
|
||||||
|
STRUCTURES = CompoundStructure.objects.filter(compound__package=BBD)
|
||||||
|
rule = Rule.objects.get(package=BBD, uuid=rule_uuid)
|
||||||
|
|
||||||
|
bt_rule_name = rule.name
|
||||||
|
smirks = rule.smirks
|
||||||
|
|
||||||
|
res = True
|
||||||
|
results = []
|
||||||
|
|
||||||
|
all_prods = set()
|
||||||
|
for structure in STRUCTURES:
|
||||||
|
ambit_smiles, ambit_errors, rdkit_smiles, rdkit_errors = run_both_engines(
|
||||||
|
structure.smiles, smirks
|
||||||
|
)
|
||||||
|
|
||||||
|
for x in ambit_smiles:
|
||||||
|
all_prods.add(x)
|
||||||
|
|
||||||
|
# TODO mode "intersection"
|
||||||
|
# partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0)
|
||||||
|
# FAILED (failures=18)
|
||||||
|
|
||||||
|
# TODO mode = "full ambit"
|
||||||
|
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(set(ambit_smiles))
|
||||||
|
# FAILED (failures=34)
|
||||||
|
|
||||||
|
# TODO mode = "equality"
|
||||||
|
partial_res = set(ambit_smiles) == set(rdkit_smiles)
|
||||||
|
# FAILED (failures=30)
|
||||||
|
|
||||||
|
if len(ambit_smiles) or len(rdkit_smiles):
|
||||||
|
temp = {
|
||||||
|
"url": structure.url,
|
||||||
|
"id": str(structure.uuid),
|
||||||
|
"name": structure.name,
|
||||||
|
"initial_smiles": structure.smiles,
|
||||||
|
"ambit_smiles": sorted(list(ambit_smiles)),
|
||||||
|
"rdkit_smiles": sorted(list(rdkit_smiles)),
|
||||||
|
"status": set(ambit_smiles) == set(rdkit_smiles),
|
||||||
|
}
|
||||||
|
detail = f"""
|
||||||
|
BT: {bt_rule_name}
|
||||||
|
SMIRKS: {smirks}
|
||||||
|
Compound: {structure.smiles}
|
||||||
|
Compound URL: {structure.url}
|
||||||
|
Num ambit: {len(set(ambit_smiles))}
|
||||||
|
Num rdkit: {len(set(rdkit_smiles))}
|
||||||
|
Num Intersection A: {len(set(ambit_smiles).intersection(set(rdkit_smiles)))}
|
||||||
|
Num Intersection B: {len(set(rdkit_smiles).intersection(set(ambit_smiles)))}
|
||||||
|
Difference A: {set(ambit_smiles).difference(set(rdkit_smiles))}
|
||||||
|
Difference B: {set(rdkit_smiles).difference(set(ambit_smiles))}
|
||||||
|
ambit products: {ambit_smiles}
|
||||||
|
rdkit products: {rdkit_smiles}
|
||||||
|
ambit_errors: {ambit_errors}
|
||||||
|
rdkit_errors: {rdkit_errors}
|
||||||
|
"""
|
||||||
|
|
||||||
|
temp["detail"] = "\n".join([x.strip() for x in detail.split("\n")])
|
||||||
|
|
||||||
|
results.append(temp)
|
||||||
|
|
||||||
|
res &= partial_res
|
||||||
|
|
||||||
|
results = sorted(results, key=lambda x: x["status"])
|
||||||
|
context["results"] = results
|
||||||
|
context["res"] = res
|
||||||
|
context["bt_rule_name"] = bt_rule_name
|
||||||
|
return render(request, "migration_detail.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
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"])
|
||||||
0
public/__init__.py
Normal file
0
public/__init__.py
Normal file
1
public/admin.py
Normal file
1
public/admin.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Register your models here.
|
||||||
6
public/apps.py
Normal file
6
public/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PublicConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "public"
|
||||||
56
public/migrations/0001_initial.py
Normal file
56
public/migrations/0001_initial.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-29 13:32
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Package",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
model_utils.fields.AutoCreatedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"uuid",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4, unique=True, verbose_name="UUID of this object"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.TextField(default="no name", verbose_name="Name")),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.TextField(default="no description", verbose_name="Descriptions"),
|
||||||
|
),
|
||||||
|
("url", models.TextField(null=True, unique=True, verbose_name="URL")),
|
||||||
|
("kv", models.JSONField(blank=True, default=dict, null=True)),
|
||||||
|
("reviewed", models.BooleanField(default=False, verbose_name="Reviewstatus")),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "epdb_package",
|
||||||
|
"managed": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
16
public/migrations/0002_alter_package_options.py
Normal file
16
public/migrations/0002_alter_package_options.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-29 18:39
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("public", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="package",
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
]
|
||||||
25
public/migrations/0003_package_license.py
Normal file
25
public/migrations/0003_package_license.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-29 18:40
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0010_alter_userpackagepermission_package_and_more"),
|
||||||
|
("public", "0002_alter_package_options"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="package",
|
||||||
|
name="license",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="epdb.license",
|
||||||
|
verbose_name="License",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
public/migrations/__init__.py
Normal file
0
public/migrations/__init__.py
Normal file
6
public/models.py
Normal file
6
public/models.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from epdb.models import AbstractPackage
|
||||||
|
|
||||||
|
|
||||||
|
class Package(AbstractPackage):
|
||||||
|
class Meta:
|
||||||
|
db_table = "epdb_package"
|
||||||
1
public/tests.py
Normal file
1
public/tests.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Create your tests here.
|
||||||
1
public/views.py
Normal file
1
public/views.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Create your views here.
|
||||||
91
pyproject.toml
Normal file
91
pyproject.toml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
[project]
|
||||||
|
name = "envipy"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"celery>=5.5.2",
|
||||||
|
"django>=5.2.1",
|
||||||
|
"django-extensions>=4.1",
|
||||||
|
"django-model-utils>=5.0.0",
|
||||||
|
"django-ninja>=1.4.1",
|
||||||
|
"django-oauth-toolkit>=3.0.1",
|
||||||
|
"django-polymorphic>=4.1.0",
|
||||||
|
"enviformer",
|
||||||
|
"envipy-additional-information",
|
||||||
|
"envipy-ambit>=0.1.0",
|
||||||
|
"envipy-plugins",
|
||||||
|
"epam-indigo>=1.30.1",
|
||||||
|
"gunicorn>=23.0.0",
|
||||||
|
"networkx>=3.4.2",
|
||||||
|
"psycopg2-binary>=2.9.10",
|
||||||
|
"python-dotenv>=1.1.0",
|
||||||
|
"rdkit>=2025.3.2",
|
||||||
|
"redis>=6.1.0",
|
||||||
|
"requests>=2.32.3",
|
||||||
|
"scikit-learn>=1.6.1",
|
||||||
|
"sentry-sdk[django]>=2.32.0",
|
||||||
|
"setuptools>=80.8.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.2" }
|
||||||
|
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
|
||||||
|
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.7"}
|
||||||
|
envipy-ambit = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" }
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
ms-login = ["msal>=1.33.0"]
|
||||||
|
dev = [
|
||||||
|
"celery-stubs==0.1.3",
|
||||||
|
"django-stubs>=5.2.4",
|
||||||
|
"poethepoet>=0.37.0",
|
||||||
|
"pre-commit>=4.3.0",
|
||||||
|
"ruff>=0.13.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||||
|
fixable = ["ALL"]
|
||||||
|
# Allow unused variables when underscore-prefixed.
|
||||||
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
docstring-code-format = true
|
||||||
|
|
||||||
|
# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in selected subdirectories.
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"__init__.py" = ["E402"]
|
||||||
|
"**/{tests,docs,tools}/*" = ["E402"]
|
||||||
|
|
||||||
|
[tool.poe.tasks]
|
||||||
|
# Main tasks
|
||||||
|
setup = { sequence = ["db-up", "migrate", "bootstrap"], help = "Complete setup: start database, run migrations, and bootstrap data" }
|
||||||
|
dev = { cmd = "python manage.py runserver", help = "Start the development server", deps = ["db-up"] }
|
||||||
|
|
||||||
|
# Database tasks
|
||||||
|
db-up = { cmd = "docker compose -f docker-compose.dev.yml up -d", help = "Start PostgreSQL database using Docker Compose" }
|
||||||
|
db-down = { cmd = "docker compose -f docker-compose.dev.yml down", help = "Stop PostgreSQL database" }
|
||||||
|
|
||||||
|
# Full cleanup tasks
|
||||||
|
clean = { sequence = ["clean-db"], help = "Remove model files and database volumes (WARNING: destroys all data!)" }
|
||||||
|
clean-db = { cmd = "docker compose -f docker-compose.dev.yml down -v", help = "Removes the database container and volume." }
|
||||||
|
|
||||||
|
# Django tasks
|
||||||
|
migrate = { cmd = "python manage.py migrate", help = "Run database migrations" }
|
||||||
|
bootstrap = { shell = """
|
||||||
|
echo "Bootstrapping initial data..."
|
||||||
|
echo "This will take a bit ⏱️. Get yourself some coffee..."
|
||||||
|
python manage.py bootstrap
|
||||||
|
echo "✓ Bootstrap complete"
|
||||||
|
echo ""
|
||||||
|
echo "Default admin credentials:"
|
||||||
|
echo " Username: admin"
|
||||||
|
echo " Email: admin@envipath.com"
|
||||||
|
echo " Password: SuperSafe"
|
||||||
|
""", help = "Bootstrap initial data (anonymous user, packages, models)" }
|
||||||
|
shell = { cmd = "python manage.py shell", help = "Open Django shell" }
|
||||||
168
static/css/c3.css
Normal file
168
static/css/c3.css
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/*-- Chart --*/
|
||||||
|
.c3 svg {
|
||||||
|
font: 10px sans-serif;
|
||||||
|
-webkit-tap-highlight-color: transparent; }
|
||||||
|
|
||||||
|
.c3 path, .c3 line {
|
||||||
|
fill: none;
|
||||||
|
stroke: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c3 text {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none; }
|
||||||
|
|
||||||
|
.c3-legend-item-tile,
|
||||||
|
.c3-xgrid-focus,
|
||||||
|
.c3-ygrid,
|
||||||
|
.c3-event-rect,
|
||||||
|
.c3-bars path {
|
||||||
|
shape-rendering: crispEdges; }
|
||||||
|
|
||||||
|
.c3-chart-arc path {
|
||||||
|
stroke: #fff; }
|
||||||
|
|
||||||
|
.c3-chart-arc text {
|
||||||
|
fill: #fff;
|
||||||
|
font-size: 13px; }
|
||||||
|
|
||||||
|
/*-- Axis --*/
|
||||||
|
/*-- Grid --*/
|
||||||
|
.c3-grid line {
|
||||||
|
stroke: #aaa; }
|
||||||
|
|
||||||
|
.c3-grid text {
|
||||||
|
fill: #aaa; }
|
||||||
|
|
||||||
|
.c3-xgrid, .c3-ygrid {
|
||||||
|
stroke-dasharray: 3 3; }
|
||||||
|
|
||||||
|
/*-- Text on Chart --*/
|
||||||
|
.c3-text.c3-empty {
|
||||||
|
fill: #808080;
|
||||||
|
font-size: 2em; }
|
||||||
|
|
||||||
|
/*-- Line --*/
|
||||||
|
.c3-line {
|
||||||
|
stroke-width: 1px; }
|
||||||
|
|
||||||
|
/*-- Point --*/
|
||||||
|
.c3-circle._expanded_ {
|
||||||
|
stroke-width: 1px;
|
||||||
|
stroke: white; }
|
||||||
|
|
||||||
|
.c3-selected-circle {
|
||||||
|
fill: white;
|
||||||
|
stroke-width: 2px; }
|
||||||
|
|
||||||
|
/*-- Bar --*/
|
||||||
|
.c3-bar {
|
||||||
|
stroke-width: 0; }
|
||||||
|
|
||||||
|
.c3-bar._expanded_ {
|
||||||
|
fill-opacity: 0.75; }
|
||||||
|
|
||||||
|
/*-- Focus --*/
|
||||||
|
.c3-target.c3-focused {
|
||||||
|
opacity: 1; }
|
||||||
|
|
||||||
|
.c3-target.c3-focused path.c3-line, .c3-target.c3-focused path.c3-step {
|
||||||
|
stroke-width: 2px; }
|
||||||
|
|
||||||
|
.c3-target.c3-defocused {
|
||||||
|
opacity: 0.3 !important; }
|
||||||
|
|
||||||
|
/*-- Region --*/
|
||||||
|
.c3-region {
|
||||||
|
fill: steelblue;
|
||||||
|
fill-opacity: .1; }
|
||||||
|
|
||||||
|
/*-- Brush --*/
|
||||||
|
.c3-brush .extent {
|
||||||
|
fill-opacity: .1; }
|
||||||
|
|
||||||
|
/*-- Select - Drag --*/
|
||||||
|
/*-- Legend --*/
|
||||||
|
.c3-legend-item {
|
||||||
|
font-size: 12px; }
|
||||||
|
|
||||||
|
.c3-legend-item-hidden {
|
||||||
|
opacity: 0.15; }
|
||||||
|
|
||||||
|
.c3-legend-background {
|
||||||
|
opacity: 0.75;
|
||||||
|
fill: white;
|
||||||
|
stroke: lightgray;
|
||||||
|
stroke-width: 1; }
|
||||||
|
|
||||||
|
/*-- Title --*/
|
||||||
|
.c3-title {
|
||||||
|
font: 14px sans-serif; }
|
||||||
|
|
||||||
|
/*-- Tooltip --*/
|
||||||
|
.c3-tooltip-container {
|
||||||
|
z-index: 10; }
|
||||||
|
|
||||||
|
.c3-tooltip {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
empty-cells: show;
|
||||||
|
-webkit-box-shadow: 7px 7px 12px -9px #777777;
|
||||||
|
-moz-box-shadow: 7px 7px 12px -9px #777777;
|
||||||
|
box-shadow: 7px 7px 12px -9px #777777;
|
||||||
|
opacity: 0.9; }
|
||||||
|
|
||||||
|
.c3-tooltip tr {
|
||||||
|
border: 1px solid #CCC; }
|
||||||
|
|
||||||
|
.c3-tooltip th {
|
||||||
|
background-color: #aaa;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
text-align: left;
|
||||||
|
color: #FFF; }
|
||||||
|
|
||||||
|
.c3-tooltip td {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-left: 1px dotted #999; }
|
||||||
|
|
||||||
|
.c3-tooltip td > span {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
margin-right: 6px; }
|
||||||
|
|
||||||
|
.c3-tooltip td.value {
|
||||||
|
text-align: right; }
|
||||||
|
|
||||||
|
/*-- Area --*/
|
||||||
|
.c3-area {
|
||||||
|
stroke-width: 0;
|
||||||
|
opacity: 0.2; }
|
||||||
|
|
||||||
|
/*-- Arc --*/
|
||||||
|
.c3-chart-arcs-title {
|
||||||
|
dominant-baseline: middle;
|
||||||
|
font-size: 1.3em; }
|
||||||
|
|
||||||
|
.c3-chart-arcs .c3-chart-arcs-background {
|
||||||
|
fill: #e0e0e0;
|
||||||
|
stroke: none; }
|
||||||
|
|
||||||
|
.c3-chart-arcs .c3-chart-arcs-gauge-unit {
|
||||||
|
fill: #000;
|
||||||
|
font-size: 16px; }
|
||||||
|
|
||||||
|
.c3-chart-arcs .c3-chart-arcs-gauge-max {
|
||||||
|
fill: #777; }
|
||||||
|
|
||||||
|
.c3-chart-arcs .c3-chart-arcs-gauge-min {
|
||||||
|
fill: #777; }
|
||||||
|
|
||||||
|
.c3-chart-arc .c3-gauge-value {
|
||||||
|
fill: #000;
|
||||||
|
/* font-size: 28px !important;*/ }
|
||||||
126
static/css/epp.css
Normal file
126
static/css/epp.css
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
.mini-submenu{
|
||||||
|
display:none;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.9);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 9px;
|
||||||
|
/*position: relative;*/
|
||||||
|
width: 42px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-submenu:hover{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-submenu .icon-bar {
|
||||||
|
border-radius: 1px;
|
||||||
|
display: block;
|
||||||
|
height: 2px;
|
||||||
|
width: 22px;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-submenu .icon-bar {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#slide-submenu{
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eductsdiv {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
#agentsdiv {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
#productsdiv {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
#actions {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.fade .modal-dialog {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog-multiple {
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
width: 600px;
|
||||||
|
margin: 30px auto;
|
||||||
|
max-height: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-element{
|
||||||
|
padding-top: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog-pps{
|
||||||
|
position: relative;
|
||||||
|
display: table;
|
||||||
|
margin-left: 35%;
|
||||||
|
margin-right: 35%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog-pps-big{
|
||||||
|
position: relative;
|
||||||
|
display: table;
|
||||||
|
margin-left: 25%;
|
||||||
|
margin-right: 25%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1220px) {
|
||||||
|
.navbar-header-framework {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
.navbar-left-framework,.navbar-right-framework {
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
.navbar-toggle-framework {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.navbar-collapse-framework {
|
||||||
|
border-top: 1px solid transparent;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.navbar-fixed-top-framework {
|
||||||
|
top: 0;
|
||||||
|
border-width: 0 0 1px;
|
||||||
|
}
|
||||||
|
.navbar-collapse-framework.collapse {
|
||||||
|
display: none!important;
|
||||||
|
}
|
||||||
|
.navbar-nav-framework {
|
||||||
|
float: none!important;
|
||||||
|
margin-top: 7.5px;
|
||||||
|
}
|
||||||
|
.navbar-nav-framework>li {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
.navbar-nav-framework>li>a {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.collapse-framework.in{
|
||||||
|
display:block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
static/images/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 |
BIN
static/images/ealogo.gif
Normal file
BIN
static/images/ealogo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
BIN
static/images/enviPy-screenshot.png
Normal file
BIN
static/images/enviPy-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user