Robert Helewka r

django-heluca-themis (1.19.0)

Published 2026-09-18 20:24:17 +00:00 by r

Installation

pip install --index-url https://git.helu.ca/api/packages/r/pypi/simple/ --extra-index-url https://pypi.org/simple django-heluca-themis

About this package

Django app providing user preferences, theme management, API key management, Casdoor SSO adapters, and standard navigation templates

Themis

Reusable Django app providing user preferences, DaisyUI theme management, API key management, and standard navigation templates for all Heluca applications.

Themis — titan of order, custom, and law.

Features

  • User Preferences — timezone (home + traveling), date/time/number formatting, week start day
  • Heluca Design System — the V2 system vendored from @heluca/svelte: token layer + daisyUI 5 bridge, light / dark plus the five temperaments (consulting, business, business-dark, terminal, terminal-light), self-hosted Marcellus, Literata, Public Sans, IBM Plex Sans, JetBrains Mono and Atkinson Hyperlegible; separate light/dark selection, auto (system) mode
  • Notifications — in-app notification bell, JS polling, browser desktop notifications, user preferences
  • API Key Management — encrypted storage with per-key instructions and documentation links
  • Standard Navigation — consistent navbar, user menu, notification bell, theme toggle, and bottom nav across all apps
  • Themed Auth Pages — every django-allauth page (login, signup, logout, password reset/change, email management, …) rendered in the Themis DaisyUI chrome, with no per-app templates
  • Middleware — automatic timezone activation and theme context
  • Formatting Utilities — date, time, number formatting respecting user preferences
  • Health Checks — Kubernetes-ready /ready/ and /live/ endpoints
  • Observability Wiring — an <app>_build_info{version="..."} metric self-reporting the deployed release, and structured event=login_success / event=login_failure lines on the themis.auth logger, so one estate-wide Loki rule covers every app
  • REST API — complete API for profiles, keys, and notifications

Installation

Releases are wheels on the estate's Gitea PyPI registry (anonymous read):

pip install django-heluca-themis --extra-index-url https://git.helu.ca/api/packages/r/pypi/simple/

Consumers pin a range in pyproject.toml (django-heluca-themis>=1.13,<2.0) and set PIP_EXTRA_INDEX_URL in the Dockerfile rather than passing the flag.

Quick Start

  1. Add to INSTALLED_APPS:
INSTALLED_APPS = [
    ...
    "rest_framework",
    "themis",
    ...
]
  1. Configure middleware:
MIDDLEWARE = [
    ...
    "themis.middleware.TimezoneMiddleware",
    "themis.middleware.ThemeMiddleware",
    ...
]
  1. Configure context processors:
TEMPLATES = [{
    "OPTIONS": {
        "context_processors": [
            ...
            "themis.context_processors.themis_settings",
            "themis.context_processors.user_preferences",
            "themis.context_processors.notifications",
            "themis.context_processors.navigation",
        ],
    },
}]
  1. Include URLs:
urlpatterns = [
    ...
    path("", include("themis.urls")),
    path("api/v1/", include("themis.api.urls")),
    ...
]
  1. Configure app settings:
THEMIS_APP_NAME = "My Application"

# Register navigation entries (block-based extension can't cross {% include %},
# so the chrome iterates these instead). Each entry: label, named URL, optional
# SVG icon `d` path. Bad url_names are skipped with a warning, not a 500.
THEMIS_NAV_ITEMS = [
    {"label": "Home", "url_name": "core:home"},
    {"label": "Mail", "url_name": "mail:account_list"},
]
THEMIS_USER_MENU_ITEMS = [
    {"label": "Mail Accounts", "url_name": "mail:account_list"},
]
  1. Run migrations:
python manage.py migrate
  1. Extend the base template:
{% extends "themis/base.html" %}

{% block nav_items %}
<li><a href="{% url 'dashboard' %}">Dashboard</a></li>
{% endblock %}

{% block content %}
<h1 class="text-2xl font-bold">My App</h1>
{% endblock %}

Casdoor SSO (optional)

Themis ships generic django-allauth adapters for Casdoor OIDC. They map the Casdoor groups claim to Django groups, set is_staff from configurable staff groups, and block superusers from SSO (they must use local auth).

SOCIALACCOUNT_ADAPTER = "themis.adapters.CasdoorAccountAdapter"
ACCOUNT_ADAPTER = "themis.adapters.LocalAccountAdapter"

Optional settings:

  • THEMIS_STAFF_GROUPS — Casdoor groups granting is_staff (default ["staff", "sme", "admin"]).
  • THEMIS_GROUP_MAPPING — Casdoor group name → Django group name.
  • THEMIS_ORG_ADAPTER — dotted path to fn(user, org_identifier) invoked with the Casdoor organization claim, for apps with an Organization model. Omit it (the default) to skip organization mapping.
  • THEMIS_TENANT_ADAPTER — store mode (Agora ADR-0004): dotted path to fn(user, tenant_id, groups, tenant_name="") invoked with the tenant_id claim, the bare own-org group names and the subscriber's display name from the tenant_name claim, instead of the org adapter, whenever the tenant_id claim is present. An adapter written against the three-argument contract of 1.16.0 is detected by its signature and still called with three arguments. Helpers for the projected groups live in themis.tenancy.
  • THEMIS_REQUIRED_GROUP_PREFIX — a product slug; an SSO login holding no <prefix> / <prefix>-* group is refused before provisioning and sent to themis:no-subscription, which links to THEMIS_STORE_URL (required with the prefix; system check themis.E001). See docs/Pattern_SaaS-Tenancy_V1-03.md § Store mode.
  • THEMIS_ENCRYPTION_KEY — any non-empty string; Themis hashes it, so no format is required, but it is the passphrase for every stored UserAPIKey and a long random value is what counts. Generate one with python -c "import secrets; print(secrets.token_urlsafe(48))". Required, from the environment, no default (system check themis.E006). Identical across every process/container of one deployment, and never changed once UserAPIKey rows exist: rows are readable only under the value that wrote them, a row that no longer opens shows as undecryptable in the admin list and on the profile page (and logs event=api_key_undecryptable), and the only remedy is reissuing the keys. Independent of SECRET_KEY by design; upgrading from a release that derived the key from SECRET_KEY: set it to the SECRET_KEY value in force at the time to keep stored keys readable. Since 1.17.0 the value is stretched with PBKDF2-HMAC-SHA256 instead of being hashed once, so a deployment upgrading from an earlier Themis must run python manage.py rekey_api_keys once — the setting is unchanged but the key it derives is, and until the command runs every stored key reads undecryptable. See docs/Themis_V1-00.md § UserAPIKey.
  • THEMIS_PROFILE_SOURCE"local" (default) keeps preferences on the app's own settings page; "claims" links the app to the estate's Agora (ADR-0018): the projected preference claims (timezone, date_format, theme_mode, …) are copied into UserProfile at every login — a missing claim leaves its field alone — and the settings page turns read-only with a "Manage in Agora" link to {AGORA_URL}/profile/. AGORA_URL is required with "claims" (system check themis.E002; environment value, no default). See docs/Pattern_SSO-Allauth-Casdoor_V1-04.md § Profile Claims.
  • AGORA_URL, AGORA_PRODUCT_API_KEY — the entitlements client (themis.entitlements: get_limits, check_quota, quota_blocked, report_usage, flush_usage). Both from the environment, no defaults, set together (system check themis.E004); the usage PUT is named by THEMIS_REQUIRED_GROUP_PREFIX (themis.E005). Optional: THEMIS_TENANT_RESOLVER (dotted path fn(request) -> tenant id; default reads request.tenant.uuid), THEMIS_QUOTA_WARN_RATIO (0.8). Add themis.context_processors.entitlements for the warn banner and schedule manage.py flush_usage every minute. See docs/Pattern_Entitlements_V1-00.md.
  • THEMIS_DISTRIBUTION — this app's [project] name from its own pyproject.toml (e.g. "kairos"). Themis exports <app>_build_info{version="..."} 1 on the Prometheus registry at startup, reading the version from the installed package metadata, so each environment self-reports the release it runs. Unset, nothing is registered and system check themis.W004 says so (only where prometheus_client is installed). An in-repo constant, not an environment value. See docs/Pattern_Observability_V2-01.md § Required Interface.
  • THEMIS_GIT_SHA — optional; when set, adds a git_sha label to <app>_build_info. From the image build, no default.

Reconnecting an account after an IdP reset

OIDC matches a returning user on the subject id allauth stored as SocialAccount.uid — for Casdoor, the user's id. Re-provisioning Casdoor re-issues those ids, so every stored connection is orphaned and SSO login falls through to auto-signup. Re-point it:

# Preview the change (old -> new uid), write nothing
python manage.py reconnect_socialaccount user@example.com --uid <new-casdoor-id> --dry-run

python manage.py reconnect_socialaccount user@example.com --uid <new-casdoor-id>

Resolves the user by username or email, creates the connection when none exists, no-ops when the uid already matches, and refuses when that uid belongs to another user. --provider defaults to casdoor — the provider_id allauth stores, not openid_connect. Full procedure, including how to do it without this command: SSO with Allauth & Casdoor.

For sandbox environments with self-signed Casdoor certs, import the SSL bypass at the top of settings.py before any requests are made (activates only when CASDOOR_SSL_VERIFY=false):

import themis.ssl_patch  # noqa: F401

Documentation

🐾 Red Panda Approval™

This project follows Red Panda Approval standards — our gold standard for Django application quality.

The 5 Sacred Django Criteria

  1. Fresh Migration Test — Clean migrations from empty database
  2. Elegant Simplicity — No unnecessary complexity
  3. Observable & Debuggable — Proper logging and error handling
  4. Consistent Patterns — Follow Django conventions
  5. Actually Works — Passes all checks and serves real user needs

License

MIT License — see LICENSE file for details.

Author

Robert Helewka r@helu.ca

Package Information

  • Package Name: django-heluca-themis
  • Django: >=5.2, <6.0
  • Python: >=3.10

Requirements

Requires Python: >=3.10
Details
PyPI
2026-09-18 20:24:17 +00:00
153
MIT
797 KiB
Assets (2)
Versions (17) View all
1.23.0 2026-09-20
1.22.0 2026-09-20
1.21.0 2026-09-19
1.20.0 2026-09-18
1.19.0 2026-09-18