Skip to main content

servicetrees

servicetrees is a collection of reusable Django apps for content-driven websites. It provides the common machinery for running one or many sites from a Django project: domain-aware settings, editor roles, flexible pages, media, search, redirects, translations, forms, cookie consent, and OpenID Connect.

It is a toolkit rather than a finished CMS. A project chooses the apps it needs, supplies its public design and base templates, and can register its own content types and page sections. The package supplies the data models, editor workflows, extension points, and conservative security defaults.

How the pieces fit together

  • A Django Site identifies each domain or subdomain. domains resolves the current site and stores its settings and editors.
  • displaypage provides layout-built pages. Its sections are polymorphic, and any project model can opt in to the same section system with HasSections.
  • mediaapp, links, search, and translations provide shared services to those pages and to project-owned content models.
  • Registries and ST_<APP>_<SETTING> settings are the public extension points. Projects do not need to fork the package to add content or customize policy.
  • Templates and static files are deliberately overridable through Django's normal template and static-file discovery.
App What it does
servicetrees.domains Sites and their settings, site resolution from the host, editors and their roles per site, dashboard and settings page, content registry
servicetrees.displaypage Layout builder: DisplayPage, polymorphic sections that can belong to any model (HasSections), native section types
servicetrees.mediaapp Media library: hashed, deduplicated uploads, browser modal, usage tracking
servicetrees.links Redirects, broken-link scanning and tracking
servicetrees.search Site search over configured models
servicetrees.translations Editable UI strings per site ({% t %})
servicetrees.ckeditor CKEditor 5 widget and configs
servicetrees.cookie_consent Klaro cookie consent
servicetrees.openid_connect OpenID Connect login for existing users
servicetrees.userprofiles User profiles
servicetrees.webform Form builder with encrypted submissions
servicetrees.error_handling Logged, deduplicated errors

App labels are st_<app> (tables st_displaypage_…); settings are ST_<APP>_<NAME>. Each app's settings and defaults are in its SETTINGS.md.

Status

Unmaintained and provided as-is. There is no commitment to updates, security fixes, or support.

Pull requests are welcome and may be looked at, but we are under no pressure to review or merge them. Contributions are licensed under the same license as this project.

This repository is MIT licensed. Font Awesome Free and Klaro, fetched at build time, keep their own licences.

Install

pip install servicetrees            # extras: servicetrees[xlsx,video]

The package supports Python 3.10+ and Django 5.2. Start with a normal Django project that has django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, and django.contrib.sites configured.

From a git checkout, fetch the third-party front-end files first (they are not in git; wheels include them):

python scripts/fetch_vendor.py

Setup

INSTALLED_APPS += [
    'django.contrib.sites',
    'polymorphic',
    'django_ckeditor_5',
    'servicetrees.domains',
    'servicetrees.displaypage',
    'servicetrees.mediaapp',
    'servicetrees.links',
    'servicetrees.search',
    'servicetrees.translations',
    'servicetrees.ckeditor',
    'servicetrees.cookie_consent',
    'servicetrees.userprofiles',
    'servicetrees.error_handling',
    'servicetrees.openid_connect',   # optional
    'servicetrees.webform',          # optional
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'servicetrees.domains.middleware.WWWRedirectMiddleware',
    'servicetrees.domains.middleware.DynamicCookieDomainSessionMiddleware',   # replaces SessionMiddleware
    'django.middleware.common.CommonMiddleware',
    'servicetrees.domains.middleware.DynamicCookieDomainCsrfMiddleware',      # replaces CsrfViewMiddleware
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'servicetrees.openid_connect.middleware.OIDCLoginInterceptorMiddleware',  # with openid_connect
    'servicetrees.domains.middleware.SubdomainMiddleware',                    # sets request.site
    'servicetrees.links.middleware.LinkRedirectMiddleware',
]

AUTHENTICATION_BACKENDS = [
    'servicetrees.openid_connect.backend.OIDCAuthBackend',  # with openid_connect
    'django.contrib.auth.backends.ModelBackend',
    'servicetrees.domains.backends.SitePermissionBackend',  # user.has_perm(perm, site)
]

# FileField / ImageField uploads go into the media library (optional).
STORAGES = {
    'default': {'BACKEND': 'servicetrees.mediaapp.storage.MediaLibraryStorage'},
    'staticfiles': {'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage'},
}

ST_DOMAINS_BASE_DOMAIN = 'example.org'

Then include the URLs you use, run migrations, and create a Site row for the domain you will serve:

python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

For a first integration, begin with domains, displaypage, and mediaapp. Add the other apps only when the project needs them. Every configurable value and its default is documented in the relevant app's SETTINGS.md.

Context processors: servicetrees.domains.context_processors.domain_settings, servicetrees.translations.context_processors.translation_overrides, servicetrees.cookie_consent.context_processors.cookie_consent.

# urls.py: include the apps you installed
from servicetrees.mediaapp import views as media_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('domains/', include('servicetrees.domains.urls')),
    path('displaypage/', include('servicetrees.displaypage.urls')),
    path('api/media/', include('servicetrees.mediaapp.urls')),
    path('links/', include('servicetrees.links.urls')),
    path('search/', include('servicetrees.search.urls')),
    path('translations/', include('servicetrees.translations.urls')),
    path('cookie-consent/', include('servicetrees.cookie_consent.urls')),
    path('profile/', include('servicetrees.userprofiles.urls')),
    path('oidc/', include('servicetrees.openid_connect.urls')),   # with openid_connect
    path('webform/', include('servicetrees.webform.urls')),       # with webform
    # CKEditor uploads into the media library (the editor scripts post to these exact paths)
    path('ckeditor5/image_upload/', media_views.MediaImageUploadView.as_view(), name='ck_editor_5_upload_file'),
    path('ckeditor5/video_upload/', media_views.MediaVideoUploadView.as_view(), name='ckeditor_video_upload'),
]

Templates of the editor pages extend base.html; the project provides it, with the blocks title, extra_head, extra_css, body_class and content. Error pages use the project's error.html if there is one.

Sections on any model

from servicetrees.displaypage.models import HasSections

class Event(HasSections):
    ...
    def user_can_edit_sections(self, user, site=None):
        return user.has_perm('st_domains.edit_site_content', site)

event.sections are its sections; the helpers in servicetrees.displaypage.views (section_add, section_reorder, section_move, section_delete, section_toggle_visibility) take owner=event. Sites register their own section types with displaypage.registry.register_section_type.

Security notes

  • Editor HTML is cleaned on the server when it is saved through the editor widget (servicetrees.ckeditor.sanitize, nh3). Content written another way, such as an import script, is stored as given. ST_CKEDITOR_SANITIZER replaces the cleaner.
  • Behind reverse proxies, set ST_TRUSTED_PROXY_COUNT to the number of proxies that append to X-Forwarded-For; otherwise REMOTE_ADDR is the client IP.
  • Each site sees only its own media unless ST_MEDIAAPP_SITE_SCOPED = False.
  • OpenID Connect links a first login to an existing user by sub only, unless ST_OIDC_USERNAME_CLAIMS names claims your provider's admins control.
  • The webform builder and submission views allow staff only unless the project passes can_edit.
  • Serve MEDIA_ROOT with X-Content-Type-Options: nosniff. A separate media domain is safer still.
  • The package's scripts send the CSRF token from the page. Put <meta name="csrf-token" content="{{ csrf_token }}"> in the <head> of pages editors use; without it they fall back to a form's token, then the cookie.

Editors and roles

A domain user has roles on one site. The default roles, "Edit pages" and "Manage domain", grant st_domains.edit_site_content and st_domains.manage_site; ST_DOMAINS_ROLES adds roles or changes what they allow (each role is a group site-role:<key> kept in step after migrate).

Developing from a checkout

Install the repository as an editable dependency, fetch the pinned front-end assets, and run the host project's checks and tests:

pip install -e .
python scripts/fetch_vendor.py
python scripts/settings_docs.py
python manage.py check
python manage.py test servicetrees

scripts/settings_docs.py regenerates the per-app settings references from their conf.py defaults. A wheel build fetches the pinned Font Awesome and Klaro releases automatically and includes them in the wheel.

The DEBUG-only Django helper for the csstweaks browser extension is maintained separately as csstweaks-client.

Developed by Vasudeva Server

Metadata

Release files for servicetrees 0.2.0

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

Built distribution (wheel)

Table of built distributions (wheels) for servicetrees 0.2.0
File Interpreter ABI Platform
servicetrees-0.2.0-py3-none-any.whl Python 3 none any Details

Release files / servicetrees-0.2.0-py3-none-any.whl

Download URL servicetrees-0.2.0-py3-none-any.whl
Size 1.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
74e5e59550699441db73fb840e445e8351ab2ced8c72808646065ee54473efe9
BLAKE2b-256 checksum
How to use checksums
be2237c57014c1bf525476daf3afd16e538098af9c55eebe8cc4a80722100296
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.17

Security for servicetrees 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

1 release file No provenance

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