# 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`). #### HTMX input - When using htmx input for autoupdating search/other triggers triggers do not use changed in the hx-trigger. Using input is sufficient. e.g. instead of hx-trigger="input changed delay:300ms, focus" use hx-trigger="input delay:300ms, focus" ### 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. - **Multi line comment** NEVER use {# ... #} style comments for multiline comments as it will render into the html, it is designed for single line comments only. Use {% comment "Optional note: You can add a label here if you want" %} This is a multiline comment. None of this text or any {% tags %} inside here will be rendered or visible in the browser's "View Source". {% endcomment %} Instead ## 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