From 854f265623a5dd0650c08f9c4a5108c29fb39dc5 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 27 Apr 2026 10:04:56 +0100 Subject: [PATCH] Add agent guidelines documentation for RAD project architecture, models, views, templates, testing, and deployment --- AGENTS.md | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..0869004f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,116 @@ +# RAD Project — Agent Guidelines + +Django-based radiology education platform. Multi-app, HTMX-driven, PostgreSQL + Redis + Celery stack. + +## Architecture + +### Apps +`generic` · `anatomy` · `physics` · `rapids` · `shorts` · `longs` · `sbas` · `wally` · `atlas` · `rcr` · `rota` · `oef` + +- `generic/` — shared base models, views, forms, mixins, decorators, widgets. **Always check here before adding new base classes or utilities.** +- `atlas/` — CaseCollection-based learning (DICOM cases, findings, display sets, differentials) +- `rad/` — project config (`settings.py`, `urls.py`, `celery.py`, root API) + +### Request/Response Pattern +- HTMX-first: `if request.htmx:` returns a partial, else returns a full page. +- Full pages extend `base.html`. HTMX partials live in `{app}/templates/{app}/partials/`. +- Partials must be standalone fragments — no `{% extends %}`. +- HTMX detection via `django_htmx` middleware (`request.htmx`). + +### API +- Django Ninja (not DRF). Routers in `{app}/api.py`, registered in `rad/api.py` at `/api/v1/`. +- Use `ModelSchema` with explicit `Meta.fields` lists (avoid `"__all__"` on public endpoints). + +## Models + +- **Auto-timestamp**: use `created_at = models.DateTimeField(auto_now_add=True)` — match the convention already on the model you are editing. +- **Primary keys**: default Django `AutoField` (integer). Do not switch to UUID without discussion. +- **Audit trail**: wrap models that need history with `@reversion.register()`. +- **Base classes** in `generic/models.py`: + - `QuestionBase`, `ExamBase`, `UserAnswerBase` — exam content + - `AuthorMixin` — M2M authors field + - `CidUser`, `CidUserGroup` — cohort-based access +- After any model change: run `python manage.py makemigrations && python manage.py migrate`. + +## Views + +- Mix of FBVs and CBVs. Prefer the pattern already used in the app you are editing. +- **Login**: `@login_required` (FBV) or `LoginRequiredMixin` (CBV). +- **Permission decorators** in `generic/decorators.py`: `@user_is_cid_user_manager`, `@check_user_in_group`. +- **Key CBV mixins** in `generic/views.py`: `CidManagerRequiredMixin`, `AuthorRequiredMixin`, `CheckCanEditMixin`. +- Do not expose `can_edit=True` to candidate-facing views. Management and candidate views are intentionally separate. +- When editing views please update docstrings to include their scope and functionality. + +## Templates + +- **Inheritance**: all full pages use `{% extends "base.html" %}`. +- **Partials path**: `{app}/templates/{app}/partials/_fragment_name.html` (prefix with `_`). +- **Template tags**: always load `{% load static %}`, `{% load django_htmx %}`, `{% load crispy_forms_tags %}` as needed at the top of the file. +- **Bootstrap 5** (dark theme). Use existing utility classes; do not add inline styles. +- To include a partial: `{% include 'app/partials/_fragment.html' %}`. Pass context explicitly when using `with` keyword. + +## Forms + +- **Crispy Forms** with `crispy_bootstrap5`. All forms should use a `FormHelper`. +- `Layout`, `Fieldset`, `Div`, `Row`, `Column` from `crispy_forms.layout` for structure. +- Custom widgets in `generic/widgets.py` — check before creating new widgets. +- Autocomplete fields use `django-autocomplete-light` (`dal`). + +## Testing + +- **Runner**: `pytest` (config in `pytest.ini`; `--nomigrations` is on by default). +- **Files**: `{app}/tests/test_*.py` or `{app}/tests.py`. +- **Fixtures**: shared fixtures in `{app}/tests/conftest.py`. Check `generic/tests/conftest.py` for common fixtures (`create_user`, `create_examination`, etc.). +- Do not use `TestCase` unless necessary — prefer plain `pytest` functions with `@pytest.mark.django_db`. +- Run tests: `pytest` or `pytest {app}/tests/`. + +## Celery / Tasks + +- Tasks autodiscovered from `{app}/tasks.py`. +- Broker and result backend: Redis at `redis://redis:6379` (Docker service name). +- Import the shared Celery app: `from rad.celery import app as celery_app`. + +## Static & Media + +- Static: `python manage.py collectstatic --noinput`. App static dirs at `{app}/static/`. +- Media: custom `HashedFilenameFileSystemStorage`. Do not assume filenames are original. +- DICOM thumbnails via `easy_thumbnails` + `helpers.images.pil_dicom_image`. + +## Settings & Secrets + +- **Never hardcode secrets**. Use `settings_local.py` (gitignored) or environment variables. +- Env vars: `SECRET_KEY`, `DB_NAME/USER/PASSWORD/HOST/PORT`, `MEMCACHE_HOST`, `DEBUG`. +- Local overrides: `rad/settings_local.py` (imported at the bottom of `settings.py`). + +## Docker (Dev) + +```bash +# Start dev environment +COMPOSE_ENV=dev docker compose -f docker/docker-compose.dev.yml up -d --build + +# Run management commands inside the container +docker compose exec web python manage.py migrate +docker compose exec web python manage.py createsuperuser +``` + +Dev server runs on `http://localhost:8080` (nginx → gunicorn/runserver). + +## Key Conventions + +| Convention | Detail | +|---|---| +| HTMX partial detection | `if request.htmx: return render(...)` | +| Partial naming | `_fragment_name.html` with leading underscore | +| Permission checks | Decorators/mixins from `generic/`; never inline `if user.is_staff` in templates | +| Model timestamps | `created_at` (auto_now_add) preferred | +| API schemas | Django Ninja `ModelSchema`; explicit field lists | +| Audit trail | `@reversion.register()` on audited models | +| Logging | `loguru` (`from loguru import logger`) | +| Candidate vs management views | Keep separate; `can_edit` must not leak to candidate views | + +## Before Opening a PR + +1. `pytest` — all tests pass +2. `python manage.py makemigrations --check` — no missing migrations +3. `python manage.py check` — no system check errors +4. No hardcoded secrets or debug prints