Metadata-Version: 2.4
Name: ichec_django_core
Version: 2.0.1
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/ichec-django-core
Project-URL: Homepage, https://git.ichec.ie/platform-engineering/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
Requires-Dist: markdown
Requires-Dist: pillow
Requires-Dist: pyyaml
Requires-Dist: pydantic
Requires-Dist: djangorestframework
Requires-Dist: django-filter
Requires-Dist: django-countries
Requires-Dist: django-cors-headers
Requires-Dist: django-downloadview
Requires-Dist: django-prometheus
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 #

This is a set of Django application building blocks and utilities for use at ICHEC. They can be used to build and test other Django apps.

Useful elements include:

* A common collection of Django Settings for use in consuming projects, intended to provide secure defaults:

``` python
from ichec_django_core import settings

MY_DJANGO_SETTING = settings.MY_DJANGO_SETTING
```

* Core functionality for authentication, including:
  * models of portal members and organizations 
  * OpenID Connect integration

* Functionality for handling user provided media and subsequent access in a secure and performant way

* Tested, re-usable components

Deciding whether to take a release, and what a major asks of you: [CHANGELOG.md](CHANGELOG.md).

# Usage #

This guide assumes you are comfortable building a basic Django app - if not you should create one first following the [Getting Started with Django guide](https://www.djangoproject.com/start/).

You can include this project `ichec-django-core` as a Python package in your `requirements.txt` or `pyproject.toml`.

## Example project ##

The [app](./app) directory is an example use of the module to build a minimal portal. Note that there's not much code involved — `ichec-django-core` has enough defaults to get something basic running.

You are encouraged to try it out before proceeding with the rest of the guide. You can do:

``` shell
git clone https://git.ichec.ie/platform-engineering/ichec-django-core.git
python -m venv .venv
source .venv/bin/activate
pip install .
source infra/set_dev_environment.sh
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser --no-input
python manage.py runserver
```

and open http://localhost:8000 in your browser to check it out.

## Development and tests ##

This repo uses the standard ICHEC Python toolchain: [uv](https://docs.astral.sh/uv/)
for environments and locking, and **ruff** (format + lint) + **mypy** for static
checks. The dev toolchain lives in the `dev` dependency group. Requires Python
`>=3.14`.

Set up the environment (one command; `uv sync` reads `uv.lock`):

``` shell
uv sync --group dev --extra async
```

If you use [direnv](https://direnv.net/), `direnv allow` once and the `.envrc`
activates the environment on entry automatically.

Pytest is configured in [pyproject.toml](./pyproject.toml) to use `tests.settings`
(sqlite, safe test defaults baked in), so tests run with no extra setup:

``` shell
uv run pytest                                    # unit suite (excludes tests/e2e)
uv run pytest tests/test_member.py::TestMemberView::test_self_view --no-cov
uv run python runtests.py                        # full Django runner incl. e2e
```

Static checks (run the same commands CI runs):

``` shell
uv run ruff format --check src tests   # format (apply: drop --check)
uv run ruff check src tests            # lint
uv run mypy src                        # types
```

The endpoint tests intentionally cover permission boundaries and common failure paths, not just successful CRUD. When adding API behavior, include cases for invalid payloads, missing optional nested fields, read-only fields, ownership changes, and unauthenticated access where relevant.

## Including default settings ##

The module will define some sensible default Django settings, leaving you to only define a few for a basic portal. See the sample settings in the [app](./app) directory:

``` 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)
```

Of course - if you need something different from the default `ichec-django-core` values you can override them in your own settings file.

The settings values in `ichec-django-core` come from environment variables. It is most convenient to keep a development version of these variables in a text file and then load them into your shell environment when working with the server. The [infra](./infra) directory shows an example of how to do this. We keep variables in a `dev.txt` file and use the shell script `set_dev_environment.sh` to load them using:

``` shell
source infra/set_dev_environment.sh
```

. In production, these values may be passed into a container using a `.env` file or via the container orchestration environment (e.g. compose files).

## Including Urls ##

The module comes with some useful default Django and Django Rest Framework (DRF) default views, such as for admin and user and organisation management.

To include them, using the [app](./app) directory as an example, you can do:

``` python
from django.urls import include, path
from django.views.generic.base import RedirectView
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")),
    path("", RedirectView.as_view(url='/api', permanent=True), name='index'),
]
```

This is a slightly non-standard approach for Django apps. Here we are creating a top-level DRF router and registering the `ichec_django_core` DRF API views with it. This approach allows for easier composition of DRF views from different modules.

We are using the standard Django approach to include the Django Views, namely ` path("", include("ichec_django_core.urls")),`.

The remaining view is a boilerplate redirect since the example doesn't come with an 'index.html' template and directs straight to the DRF landing instead.

## Using OpenID Connect ##

The module has basic user managment and authentication built-in, however you likely want this to be handled in a more suitable application or want to include the portal in a Single-Sign-On framework. This can be handled using OAuth 2.0 and OpenID Connect.

To set this up we first need to register our app as a client in an OIDC provider. ICHEC uses Keycloak so will focus on it in the guide, but the module itself is agnostic of the particulars of the provider.

Once the client has been registered you can set the following environment variables:

``` shell
WITH_OIDC=1
OIDC_RP_CLIENT_ID=xxx
OIDC_RP_CLIENT_SECRET=xxx
OIDC_OP_AUTHORIZATION_ENDPOINT=xxx
OIDC_OP_TOKEN_ENDPOINT=xxx
OIDC_OP_USER_ENDPOINT=xxx
OIDC_OP_JWKS_ENDPOINT=xxx
```

using [this guide](https://mozilla-django-oidc.readthedocs.io/en/stable/installation.html) as a reference. You can look at [infra/dev.txt](./infra/dev.txt) as an example if running a local Keycloak provider, just the client ID and secret need to be changed.

## Syncing attributes back to Keycloak ##

Optionally, the portal can push derived user attributes (role, avatar, ...) *back*
onto the Keycloak user so other OIDC clients can read them at login. It is off
unless `KEYCLOAK_ATTRIBUTE_SYNC_ENABLED=1`, needs the `keycloak` extra, and uses a
service-account client (no admin password). A consuming app declares *what* to
sync with a `KEYCLOAK_ATTRIBUTE_SYNC` map of `{keycloak_attribute: "dotted.path.to.callable"}`,
where each callable takes a user and returns the value (or `None` to leave that
attribute untouched). A built-in `picture` deriver projects a member's avatar,
preferring a locally uploaded image over the one the upstream IdP provided.


# Licensing #

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