Developing MedUX Plugins¶
MedUX plugins are independent Python packages that extend MedUX via
GDAPS. A plugin depends on medux,
which transitively pulls in conjunto and gdaps.
TL;DR¶
curl -sSL https://gitlab.com/nerdocs/medux/medux/-/raw/main/scripts/bootstrap.sh | bash -s myplugin
cd medux-myplugin
source .venv/bin/activate # mandatory for every session
medux migrate
medux runserver
The bootstrap script generates the plugin from the cookiecutter
template, writes a [tool.uv.sources] entry that pins medux to its
Git main branch, runs uv sync (which pulls medux + deps from Git),
creates a dev .env, and initializes a git repo. No local sibling
checkouts are cloned by default — pure plugin development does not
need them.
Activate .venv — never prefix commands with uv run
Plugin development requires the plugin's .venv to be active.
The medux CLI script lives in .venv/bin/medux and is only on
your $PATH after source .venv/bin/activate.
Do not use uv run medux … or uv run pytest … — uv run
implicitly syncs the venv against uv.lock, which also removes
any editable overlay installed by make dev-local. You would then
have to re-run make dev-local.
uv is still used for dependency management (uv sync, uv add,
uv lock) — just not for running tools.
Alpha phase: dependencies come from Git main¶
MedUX, Conjunto, and GDAPS have no PyPI releases yet. The plugin's
committed pyproject.toml therefore resolves medux via
[tool.uv.sources]:
[tool.uv.sources]
medux = { git = "https://gitlab.com/nerdocs/medux/medux.git", rev = "main" }
uv sync pulls the current main HEAD into the plugin's .venv.
Conjunto and GDAPS are pulled in transitively by medux's own
[tool.uv.sources]. Pure plugin development needs nothing else.
Once MedUX reaches a stable PyPI release, the Git-URL source can be
dropped and uv sync will resolve from PyPI.
Optional: local editable overlay of medux / conjunto¶
If you are also hacking on medux or conjunto itself while building
a plugin, clone them as siblings and use the editable overlay:
~/Projects/
├── medux/ # git checkout
├── conjunto/ # git checkout (optional)
└── medux-myplugin/ # this plugin
In the plugin directory, make dev-local first runs uv sync (which
installs medux/conjunto from Git main) and then overlays the
sibling checkouts as editable installs via
uv pip install --python .venv/bin/python --no-deps -e ../medux ../conjunto.
pyproject.toml and uv.lock are never touched — CI still resolves
from Git main, exactly as committed. Re-run make dev-local after
every uv sync (which wipes the overlay) or after re-activating the
venv.
To enable the overlay immediately at bootstrap time, pass
--local=medux (or --local=medux,conjunto) to bootstrap.sh; the
script then asks whether to clone any missing siblings.
Prerequisites¶
- uv (package manager / runner)
- Git
- For pure plugin development: internet access to clone the medux Git repository (HTTPS, no credentials required)
- Only if you pass
--local=...: an SSH key registered at gitlab.com for cloning sibling repositories
Bootstrap¶
Default: Git-main only, no sibling clones¶
curl -sSL https://gitlab.com/nerdocs/medux/medux/-/raw/main/scripts/bootstrap.sh | bash -s myplugin
This creates medux-myplugin/ with pyproject.toml pinned to medux's
Git main branch, runs uv sync (which fetches medux + transitive
deps from Git), writes a dev .env, and git inits the repo.
No siblings are cloned. You can start developing immediately:
cd medux-myplugin
source .venv/bin/activate
medux migrate
medux runserver
Custom sibling overlay¶
If you also hack on medux, conjunto, or gdaps while building the
plugin, request a local overlay:
bash bootstrap.sh myplugin --local=medux,conjunto -y
The script confirms or clones the named sibling repositories, runs
uv sync as usual, and then overlays the siblings as editable
installs. make dev-local reapplies this overlay after any future
uv sync.
What the script does¶
- Runs the
gl:nerdocs/gdaps-plugin-cookiecuttertemplate non-interactively (author taken fromgit config). - Replaces the generated
gdapsdependency withmeduxin[project.dependencies]and writesmedux = { git = "https://gitlab.com/nerdocs/medux/medux.git", rev = "main" }in[tool.uv.sources]. Any extra--localpackages get the same treatment. Committed state therefore always resolves from Git — no local paths leak intopyproject.toml. - Optionally (only with
--local): confirms or clones the requested sibling checkouts into the current directory. - Runs
uv sync, then (only with--local) overlays the editable sibling installs viauv pip install --python .venv/bin/python --no-deps -e ../<pkg>(same pattern asmake dev-local). git init+ installs apre-pushhook that runspytest(only when the cookiecutter shipped a.pre-commit-config.yaml).
Editable sibling overlay¶
Every generated plugin ships with a Makefile implementing the same
overlay pattern used by medux itself:
make dev-local # uv sync + editable overlay of ../medux, ../conjunto
make dev-pypi # plain uv sync — no overlay
make dev-local reinstalls medux / conjunto from sibling checkouts
on top of a Git-synced venv without touching pyproject.toml or
uv.lock. That means committing and pushing never triggers a
dependency-source switch — the committed state is always the Git-main
baseline that CI resolves via [tool.uv.sources].
Note
uv sync is authoritative and will wipe the overlay. Re-run
make dev-local after every uv sync (or after make dev-pypi)
to restore the editable sibling links.
MedUX itself offers the same overlay if you want to develop against a
local ../conjunto or ../gdaps:
cd ~/Projekte/medux
make dev-local
Plugin anatomy¶
See Plugin Structure for the directory layout
and the meaning of each file (apps.py, menus.py,
scoped_settings.py, gdaps_hooks.py, ...).
The plugin must register itself via an entry point:
[project.entry-points."medux.plugins"]
myplugin = "medux.plugins.myplugin:apps.MyPluginConfig"
Its AppConfig must subclass MeduxPluginAppConfig:
from medux.common.api import MeduxPluginAppConfig
class MyPluginConfig(MeduxPluginAppConfig):
name = "medux.plugins.myplugin"
verbose_name = "My Plugin"
class PluginMeta:
pass
Testing¶
source .venv/bin/activate
pytest # or: make test
Plugins use pytest-django. The generated tests/settings.py is a
minimal Django settings module suitable for running the plugin in
isolation. See medux-schedule
for a full testing example.
Useful commands¶
All tool commands assume the venv is active (
source .venv/bin/activate).
| Command | Description |
|---|---|
make dev-local / make dev-pypi |
Editable-sibling overlay vs plain Git-synced venv |
medux migrate |
Apply database migrations |
medux runserver |
Start the development server |
medux syncplugins |
Sync plugin metadata to the database |
medux makemigrations |
Create migrations for model changes |
medux shell |
Open a Django shell |
pytest |
Run the test suite |
ruff check . |
Run the linter |
Switching between sibling and Git resolution¶
make dev-pypi # plain uv sync — resolves medux/conjunto from Git main
make dev-local # same, then overlays ../medux / ../conjunto as editable
The overlay has zero effect on tracked files, so CI always resolves
from the [tool.uv.sources] Git URLs regardless of the local overlay
state. The dev-pypi target is named for historical reasons — during
the alpha phase it really fetches from Git main, not from PyPI.
Using an .env file¶
Drop a .env file into your plugin directory to configure the database,
debug mode, or other settings. Without one, MedUX defaults to SQLite
(db.sqlite3 in the current directory) with DEBUG=True.
Example for PostgreSQL:
DATABASE_ENGINE=django.db.backends.postgresql
DATABASE_NAME=medux_dev
DATABASE_USER=myuser
DATABASE_PASS=mypassword
DEBUG=True
Git hooks¶
The bootstrap script installs a pre-push hook that runs pytest
before every git push, so broken tests never reach the remote. The
included .pre-commit-config.yaml also runs Black, Ruff, and basic
file checks on every commit.
If you set up a plugin manually, install the hooks yourself (venv active):
git init
pre-commit install --hook-type pre-push
Troubleshooting¶
Permission denied (publickey) when bootstrapping with --local=.
Only --local=... clones via SSH. Register your SSH key at
https://gitlab.com/-/user_settings/ssh_keys and ensure
ssh -T git@gitlab.com works. The default (no --local) uses HTTPS
Git fetches via uv sync and does not need an SSH key.
'./medux' exists but is not a git repository.
Only occurs with --local=medux. You have a stray directory of that
name. Remove or rename it, then rerun the script.
uv sync fails: cannot find medux or conjunto on PyPI.
During alpha, these packages are not on PyPI yet. The committed
pyproject.toml therefore must carry a [tool.uv.sources] entry
pointing at the Git main branch (e.g.
medux = { git = "https://gitlab.com/nerdocs/medux/medux.git", rev = "main" }).
If that entry is missing, add it — otherwise uv sync has no way to
resolve the package.
The editable overlay is gone after uv sync.
That's expected — uv sync removes packages not in uv.lock. Re-run
make dev-local to reapply the overlay.
Plugin not picked up by MedUX.
With the venv active, run medux syncplugins. Verify the
[project.entry-points."medux.plugins"] entry in pyproject.toml
points at the correct AppConfig.
medux: command not found.
You forgot source .venv/bin/activate. The medux script lives in
.venv/bin/medux; it is only on your $PATH while the venv is active.
Reference implementation¶
medux-schedule is a
real MedUX plugin and the canonical reference for the layout,
pyproject.toml shape, [tool.uv.sources] pattern, and test setup.
Next steps¶
- Plugin Structure — detailed layout of a plugin
- Interfaces Reference — all extension points
- Administration Sections — contributing admin screens
- Menu System — adding menu entries