Skip to main content

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 is a complete app built on it, deployed with the ichec.platform Ansible collection.

ichec-django-core's building blocks: workflows, forms, files, people, sign-in and background tasks over a secure foundation, all through a standards-based REST API

Try the example app

The 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 (brew install uv).

git clone https://git.ichec.ie/platform-engineering/modules/web/ichec-django-core.git
cd ichec-django-core
uv run tox exec -e dev -- python manage.py migrate
uv run tox exec -e dev -- python manage.py createsuperuser
uv run tox -e dev

Sign in at http://localhost:8000/accounts/login/ as the admin you created, then open http://localhost:8000/api/ to browse the API. The dev env in pyproject.toml holds the development settings.

Use it in your app

This guide assumes you can build a basic Django app. If not, work through the Django tutorial first.

Add the library to your dependencies:

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:

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, which settings.py lists with their defaults. Django won't start without DJANGO_SECRET_KEY and DJANGO_ALLOWED_HOSTS. For local work, set them in a tox env, as the dev env in pyproject.toml does. In production, also set DJANGO_PORTAL_URL and DJANGO_DEFAULT_FROM_EMAIL: a deploy check fails without them.

Scheduled commands

Run these on a timer. The webapp_django role of the ICHEC Ansible collection does it for you.

Command When
retry_emails every 5 minutes
run_workflow_schedule every 5 minutes, with workflows
send_notification_digests daily
purge_uploads daily

Logs

The app logs JSON lines, each with the ID of the request that wrote it. It logs sign-ins, refused requests, API token use, refused uploads and admin changes as security events, each with a stable name to alert on. docs/logging.md lists the fields and the event names.

Languages

Every string the library shows people is marked for translation, and its catalogue is in src/ichec_django_core/locale. The API answers in the language a request asks for with Accept-Language, of those in LANGUAGES. Each member can choose a language, and their emails and notifications are in it. An email to several people is in the portal's default language.

To add a language, add it to LANGUAGES, and translate both the library's strings and your app's. Add the library's translation to the library, in locale/<code>/LC_MESSAGES/django.po, so every portal gets it: the en catalogue lists the strings. For your app's own strings, mark them with gettext and {% translate %}, and run python manage.py makemessages -l <code> in your app. compilemessages builds both catalogues.

URLs

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

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")),
]

handler404 = "ichec_django_core.views.errors.not_found"
handler500 = "ichec_django_core.views.errors.server_error"

Register your own viewsets on the same router, so the whole API sits under one /api/ root. If Django serves your Angular app, add spa_route() from views/spa.py last.

Say who may do what

Each model declares who may do what with it, once:

from django.db import models

from ichec_django_core.access import HasPermission, Owner, Policy


class Application(models.Model):
    class Access(Policy):
        applicant = Owner("applicant")
        manager = HasPermission("change_application")

        view = applicant | manager
        change = applicant.when(status="draft")

A viewset built on SearchableModelViewSet then lists only the rows the caller may see, and refuses what the policy doesn't allow. docs/access.md covers status changes, roles and hidden fields, and docs/members.md who sees whom.

Generate your app's client

Your API's schema, schema.yaml, and a typed Python client come from your code. Install the codegen extra, give your app a client package, and run:

python manage.py generate_client myapp.client

Commit what it writes. In CI, add --check to fail when it's out of date.

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:

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. The built-in login locks out repeated failed sign-ins, and Django must only be reachable through nginx. To sign in through a Keycloak on your machine, with a Test realm on port 8080, run uv run tox -e dev-oidc. mozilla-django-oidc does the OIDC work, and its docs explain each setting.

Optional features

Each of these is off until you turn it on. Each link says how.

  • Forms defined by JSON Schema, with drafts and file answers: docs/forms.md.
  • Workflows, review flows held as data, so each call configures its own: docs/workflows.md.
  • Background tasks on Celery: app/celery.py and tasks.
  • Facet counts beside a list's results: views/facets.py.
  • File storage through nginx or S3, resumable uploads through tusd, and malware scanning with ClamAV: docs/files.md.
  • Email delivery records, retries and metrics: docs/email.md.
  • Error reporting from the server and the browser app: set SENTRY_DSN and DJANGO_ERROR_REPORTING_DSN.
  • Branding from an ichec-brand pack: set DJANGO_BRAND_PACK to the pack's directory. The manifest, the templates and the emails get its name, logos, tokens and header and footer hints, and its images are static files under brand/. An app that sets its own STATICFILES_FINDERS adds ichec_django_core.brand.BrandFinder. brand.py says how a pack is read.
  • Keycloak attributes copied onto each member's Keycloak user: 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.
  • PolicyEnforcementTestCase checks your API against each model's policy.
  • AuthorizationSweepTestCase calls every endpoint as each role.
  • ListQueryBudgetTestCase fails any list that asks the database once per row.
  • test/privacy.py checks that erasing a member removes their personal data.
  • assert_emails_render fails on an email template that names a variable its context lacks. docs/email.md says how to use it.
  • A pytest plugin, which loads by itself. It hashes test passwords with MD5, gives each run its own temporary MEDIA_ROOT, and fails a test that leaves a file, socket, database connection or thread open. Under xdist, it migrates one test database and copies it for each worker, and it can spread a long parametrized test, such as an API conformance test, across the workers. The options are in src/ichec_django_core/pytest_plugin.py.

docs/e2e.md runs a user journey both in-process and against a deployed stack.

Work on this library

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

uv sync --group dev --extra async
uv run pytest
uv run ruff format --check .
uv run ruff check .
uv run mypy src

The tests run on SQLite locally, and on Postgres 18 in CI, as production runs it. Postgres finds faults that SQLite hides, such as a foreign key to a table that doesn't exist yet. To run them on Postgres, start a server and run the postgres env:

podman run -d --rm --name ichec-test-postgres -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:18
uv run tox -e postgres

DJANGO_SQL_HOST, DJANGO_SQL_PORT, DJANGO_SQL_USER and DJANGO_SQL_PASSWORD point it at another server.

If you use direnv, run direnv allow once and the environment activates when you enter the directory.

The messages check needs GNU gettext (brew install gettext). If you add or change a string people read, mark it for translation, and update the catalogue with uv run tox -e messages, and commit it. An app can check its own catalogue the same way, with python manage.py check_messages -l <code> run from its package.

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

uv run python manage.py generate_client ichec_django_core.client

Licence

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

Metadata

Release files for ichec-django-core 8.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ichec-django-core 8.3.0
File Size Uploaded
ichec_django_core-8.3.0.tar.gz 411.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ichec-django-core 8.3.0
File Interpreter ABI Platform
ichec_django_core-8.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 765.4 kB

Release files / ichec_django_core-8.3.0.tar.gz

Download URL ichec_django_core-8.3.0.tar.gz
Size 411.6 kB
Tags Source
SHA-256 checksum
How to use checksums
40b5ba875e56d688bc03b41c0c4a6ca8a101764e596cc820126f1453fb191e8d
BLAKE2b-256 checksum
How to use checksums
1e4c5f5232c02209ae8d08b68afba12f74786c16602c1e47ee59188e2e8dad80
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / ichec_django_core-8.3.0-py3-none-any.whl

Download URL ichec_django_core-8.3.0-py3-none-any.whl
Size 353.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
66e7b2f2d76b491af07dd94f44725c0de2f8aabf929ef3e0cb369b312bed102c
BLAKE2b-256 checksum
How to use checksums
dc475c1537644bdd97e38b296b911685bb79dddd4311543d4fa5ccc3095248c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

8.4.0

2 release files

This release

8.3.0 This release

2 release files

8.2.0

2 release files

8.1.1

2 release files

8.1.0

2 release files

8.0.1

2 release files

8.0.0

2 release files

7.7.0

2 release files

7.6.0

2 release files

7.5.0

2 release files

7.4.0

2 release files

7.3.0

2 release files

7.2.1

2 release files

7.2.0

2 release files

7.1.0

2 release files

7.0.0

2 release files

6.2.0

2 release files

6.1.0

2 release files

6.0.0

2 release files

5.2.0

2 release files

5.1.0

2 release files

5.0.0

2 release files

4.0.1

2 release files

4.0.0

2 release files

3.0.0

2 release files

2.11.2

2 release files

2.11.1

2 release files

2.11.0

2 release files

2.10.2

2 release files

2.10.1

2 release files

2.10.0

2 release files

2.9.0

2 release files

2.8.0

2 release files

2.7.0

2 release files

2.6.0

2 release files

2.5.0

2 release files

2.4.1

2 release files

2.4.0

2 release files

2.3.1

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.1.1

2 release files

2.1.0

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.8.1

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.13

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page