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
Siteidentifies each domain or subdomain.domainsresolves the current site and stores its settings and editors. displaypageprovides layout-built pages. Its sections are polymorphic, and any project model can opt in to the same section system withHasSections.mediaapp,links,search, andtranslationsprovide 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_SANITIZERreplaces the cleaner. - Behind reverse proxies, set
ST_TRUSTED_PROXY_COUNTto the number of proxies that append toX-Forwarded-For; otherwiseREMOTE_ADDRis 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
subonly, unlessST_OIDC_USERNAME_CLAIMSnames claims your provider's admins control. - The webform builder and submission views allow staff only unless the project
passes
can_edit. - Serve
MEDIA_ROOTwithX-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.
Related packages
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.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| servicetrees-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Release files / servicetrees-0.1.1-py3-none-any.whl
| Download URL | servicetrees-0.1.1-py3-none-any.whl |
|---|---|
| Size | 1.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5ad99e50ec5e587b5fd9a2f8a3e7c40bc8f52707dab9b076be131875955b9791
|
|
BLAKE2b-256 checksum How to use checksums |
1aa2a9c21d756eabf3684e7a1f8a4b73d37dadebb735acea0f3d810906922e2a
|
| 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.1.1
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.