a573ed78a4
- Redesigned the supervisor overview page to include a header card with supervisor details and trainee statistics. - Implemented a tabbed interface for viewing trainees and their recent attempts. - Improved the trainee detail page with a profile card and a table for displaying attempt history. - Added a new view for supervisors to provide feedback on case collections, including outstanding feedback items and previous conversations. - Updated URL routing to support new feedback functionality. - Enhanced backend logic to ensure proper sharing permissions for exam attempts and case collections. - Added comprehensive tests for supervisor access to trainee data and feedback functionalities.
161 lines
7.8 KiB
Markdown
161 lines
7.8 KiB
Markdown
# 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/`.
|
|
|
|
## Exceptions
|
|
- Avoid unnecessary try: except blocks.
|
|
- We should not generate empty excepty blocks, if there is an error or bug this should be surfaced (even if just to the logs).
|
|
|
|
## 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)
|
|
|
|
### Start Dev Environment
|
|
Start the development server with local configurations:
|
|
```bash
|
|
COMPOSE_ENV=dev docker compose -f docker/docker-compose.local.yml -f docker/docker-compose.dev.yml up -d --build
|
|
```
|
|
|
|
### Run Django Management Commands inside the Container
|
|
Since PostgreSQL and Redis run in docker, run all database/management commands within the web container:
|
|
```bash
|
|
# Run database migrations
|
|
COMPOSE_ENV=dev docker compose -f docker/docker-compose.local.yml -f docker/docker-compose.dev.yml exec web python manage.py migrate
|
|
|
|
# Generate new migrations
|
|
COMPOSE_ENV=dev docker compose -f docker/docker-compose.local.yml -f docker/docker-compose.dev.yml exec web python manage.py makemigrations
|
|
|
|
# Check if migrations are missing (useful for CI/pre-commit)
|
|
COMPOSE_ENV=dev docker compose -f docker/docker-compose.local.yml -f docker/docker-compose.dev.yml exec web python manage.py makemigrations --check
|
|
|
|
# Run system checks
|
|
COMPOSE_ENV=dev docker compose -f docker/docker-compose.local.yml -f docker/docker-compose.dev.yml exec web python manage.py check
|
|
|
|
# Create a Django superuser
|
|
COMPOSE_ENV=dev docker compose -f docker/docker-compose.local.yml -f docker/docker-compose.dev.yml exec web python manage.py createsuperuser
|
|
```
|
|
|
|
### Runserver and service commands
|
|
To restart the web server/runserver or tail the container output:
|
|
```bash
|
|
# Restart the runserver/web service container
|
|
COMPOSE_ENV=dev docker compose -f docker/docker-compose.local.yml -f docker/docker-compose.dev.yml restart web
|
|
|
|
# View container logs (interactive)
|
|
COMPOSE_ENV=dev docker compose -f docker/docker-compose.local.yml -f docker/docker-compose.dev.yml logs -f web
|
|
```
|
|
|
|
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
|