Metadata-Version: 2.4
Name: ichec_django_core
Version: 3.0.0
Summary: Library of base Django app building blocks and utilities.
Author-email: Irish Centre for High End Computing <platformengineering@ichec.ie>
License: MIT
Project-URL: Repository, https://git.ichec.ie/platform-engineering/modules/web/ichec-django-core
Project-URL: Homepage, https://git.ichec.ie/platform-engineering/modules/web/ichec-django-core
Keywords: Web Application,Django
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Requires-Python: >=3.14
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: django>=6.0
Requires-Dist: markdown
Requires-Dist: pillow
Requires-Dist: pyyaml
Requires-Dist: pydantic
Requires-Dist: djangorestframework>=3.18
Requires-Dist: django-filter
Requires-Dist: django-countries
Requires-Dist: django-cors-headers
Requires-Dist: django-downloadview
Requires-Dist: django-prometheus>=2.5
Requires-Dist: mozilla-django-oidc
Requires-Dist: drf-spectacular>=0.30.0
Provides-Extra: async
Requires-Dist: celery>=5.3; extra == "async"
Requires-Dist: redis>=5.0; extra == "async"
Provides-Extra: container
Requires-Dist: psycopg2-binary; extra == "container"
Requires-Dist: gunicorn; extra == "container"
Provides-Extra: s3
Requires-Dist: django-storages[s3]; extra == "s3"
Provides-Extra: keycloak
Requires-Dist: python-keycloak; extra == "keycloak"
Provides-Extra: types
Requires-Dist: types-requests; extra == "types"
Dynamic: license-file

# ICHEC Django Core

`ichec-django-core` is the base for ICHEC's Django web apps. It gives your app
secure default settings, member and organisation models, sign-in through
Keycloak, and a REST API for all of these. You write the parts that are specific
to your app.

The [reference app](https://git.ichec.ie/platform-engineering/infrastructure/reference-app)
is a complete app built on it, deployed with the `ichec.platform` Ansible
collection.

## Try the example app

The [app](./app) directory is a minimal portal built on the library. Run it
locally to see what you get before you write any code. You need
[uv](https://docs.astral.sh/uv/) (`brew install uv`).

```shell
git clone https://git.ichec.ie/platform-engineering/modules/web/ichec-django-core.git
cd ichec-django-core
uv sync
source infra/set_dev_environment.sh
uv run python manage.py migrate
uv run python manage.py createsuperuser --no-input
uv run python manage.py runserver
```

Sign in at <http://localhost:8000/accounts/login/> as `site_admin` with password
`abc123`, then open <http://localhost:8000/api/> to browse the API.
`set_dev_environment.sh` loads these and the other development settings from
[infra/dev.txt](./infra/dev.txt).

## Use it in your app

This guide assumes you can build a basic Django app. If not, work through the
[Django tutorial](https://www.djangoproject.com/start/) first.

Add the library to your dependencies:

```shell
uv add ichec-django-core
```

### Settings

Import the library's settings, then set the few values that depend on your
project layout. Your settings file then looks like [app/settings.py](./app/settings.py):

```python
from pathlib import Path

from ichec_django_core import settings
from ichec_django_core.settings import *

BASE_DIR = Path(__file__).resolve().parent.parent

ROOT_URLCONF = "app.urls"
WSGI_APPLICATION = "app.wsgi.application"
ASGI_APPLICATION = "app.asgi.application"

TEMPLATES = settings.get_templates(BASE_DIR)
DATABASES = settings.get_databases(BASE_DIR)
STATIC_ROOT = settings.get_static_root(BASE_DIR)
MEDIA_ROOT = settings.get_media_root(BASE_DIR)
```

Override any other setting below the import.

The library reads its settings from environment variables. Django won't start
without `DJANGO_SECRET_KEY` and `DJANGO_ALLOWED_HOSTS`, or without
`DJANGO_SQL_PASSWORD` when it uses a database server rather than SQLite.
For local work, keep the variables in a file and load them into your shell, as
[infra/set_dev_environment.sh](./infra/set_dev_environment.sh) does. In
production, the deployment passes them to the container.
[settings.py](./src/ichec_django_core/settings.py) lists every variable and its
default.

### URLs

Register the library's API views on your own router, and include its other views,
as in [app/urls.py](./app/urls.py):

```python
from django.urls import include, path
from rest_framework import routers

from ichec_django_core.urls import register_drf_views

router = routers.DefaultRouter()
register_drf_views(router)

urlpatterns = [
    path("api/", include(router.urls)),
    path("", include("ichec_django_core.urls")),
]
```

Register your app's own viewsets on the same router, so the whole API sits under
one `/api/` root.

## Sign in with Keycloak

The example app uses Django's own login, which lets anyone register. Real
deployments sign users in through Keycloak with OpenID Connect (OIDC).

Register your app as a client in the Keycloak realm, then set:

```shell
WITH_OIDC=1
OIDC_RP_CLIENT_ID=my-app
OIDC_RP_CLIENT_SECRET=...
OIDC_OP_AUTHORIZATION_ENDPOINT=https://keycloak.example.org/realms/my-realm/protocol/openid-connect/auth
OIDC_OP_TOKEN_ENDPOINT=https://keycloak.example.org/realms/my-realm/protocol/openid-connect/token
OIDC_OP_USER_ENDPOINT=https://keycloak.example.org/realms/my-realm/protocol/openid-connect/userinfo
OIDC_OP_JWKS_ENDPOINT=https://keycloak.example.org/realms/my-realm/protocol/openid-connect/certs
OIDC_OP_LOGOUT_ENDPOINT=https://keycloak.example.org/realms/my-realm/protocol/openid-connect/logout
```

Leave `DJANGO_WITH_USER_LOGIN` unset in production.
[infra/dev_oidc.txt](./infra/dev_oidc.txt) has the values for a Keycloak running
on your machine. Load them with `source infra/set_oidc_environment.sh`.
[mozilla-django-oidc](https://mozilla-django-oidc.readthedocs.io/en/stable/installation.html)
does the OIDC work, and its docs explain each setting.

## Optional features

Each of these is off until you turn it on.

- **Background tasks.** Emails and thumbnails run inline by default. To run them
  on a Celery worker, install the `async` extra and set
  `DJANGO_TASK_BACKEND=celery`, `CELERY_BROKER_URL` and `CELERY_RESULT_BACKEND`.
  Then call `create_celery_app()` in your app's `celery.py`, as in
  [app/celery.py](./app/celery.py), and run `celery -A your_app worker`. To
  run your own functions the same way, decorate them with
  `@background_task("your_app.task_name")` from `ichec_django_core.tasks`.
- **File storage.** Uploaded files are served by Django by default. Set
  `DJANGO_DOWNLOAD_DELIVERY` to `xaccel` to have nginx serve them, or to `s3` to
  keep them in object storage. The comments in
  [settings.py](./src/ichec_django_core/settings.py) explain each mode.
- **Keycloak attributes.** The app can copy values such as a member's role or
  avatar onto their Keycloak user, so other apps in the realm can read them.
  Set `KEYCLOAK_ATTRIBUTE_SYNC_ENABLED=1`, install the `keycloak` extra, and
  list the attributes in `KEYCLOAK_ATTRIBUTE_SYNC`. See
  [sso/\_\_init\_\_.py](./src/ichec_django_core/sso/__init__.py).

## Test your app

`ichec_django_core.test` has helpers for your app's tests:

- `AuthAPITestCase` calls your API as each test user.
- `AuthorizationSweepTestCase` calls every endpoint in your schema as each role,
  and checks the result against a file listing who may call what. The reference
  app shows it in use.
- The checks in `test/privacy.py` prove that erasing a member removes all their
  personal data, including from your app's models.

## Work on this library

Install the development tools and run the checks that CI runs:

```shell
uv sync --group dev --extra async
uv run pytest
uv run ruff format --check src tests
uv run ruff check src tests
uv run mypy src
```

`uv run python runtests.py` also runs the end-to-end tests in `tests/e2e`. If you
use [direnv](https://direnv.net/), run `direnv allow` once and the environment
activates when you enter the directory.

If you change a model or serializer, regenerate the API schema and commit it:

```shell
uv run python manage.py spectacular --file schema.yaml --validate
```

## Licence

Copyright of the Irish Centre for High End Computing (ICHEC), released under the
MIT License. See [LICENSE](./LICENSE).
