Skip to main content

django-launchpad

PyPI version Python versions CI Coverage License

Renderer-independent navigation and application launchpads for Django.

Every Django application eventually needs navigation: a sidebar, a dashboard, a mobile menu, an account menu, an application launcher, or a command palette.

Most projects build each surface separately.

django-launchpad models them as different presentations of the same navigation system.

Define destinations once. Compose them into named launchpads. Apply user-aware visibility. Resolve the result into a renderer-neutral tree. Then present that tree however your application needs.

NavigationLink
      │
      ▼
LaunchpadNode
      │
      ▼
   Launchpad
      │
      ▼
ResolvedLaunchpad
      │
      ├── Sidebar
      ├── Dashboard cards
      ├── Top navigation
      ├── Mobile menu
      ├── Footer
      └── Command palette

The package solves navigation structure, policy, resolution, and optional configuration caching.

Your project owns the user interface.


Why Launchpad?

Navigation often begins as a few hard-coded links and gradually grows into duplicated logic spread across templates, context processors, views, permission checks, and frontend components.

Common problems follow:

  • The same destination is defined repeatedly.
  • Sidebars and dashboards drift out of sync.
  • Permission checks differ between renderers.
  • Active-state logic is duplicated.
  • Project-specific templates become responsible for data loading.
  • Changing navigation requires editing application code.
  • A destination cannot be reused with different labels or placement rules.
  • Navigation configuration is queried on every request even though it changes rarely.

Launchpad separates the problem into durable layers:

  • NavigationLink — the canonical destination.
  • LaunchpadNode — one placement of a destination or structural item.
  • Launchpad — a named composition of nodes.
  • Reader — resolves stored configuration into renderer-neutral data.
  • Renderer — remains entirely project-owned.

The same destination can appear in multiple launchpads with different titles, hierarchy, ordering, visibility, metadata, icons, and enabled states—without duplicating the destination itself.


Core Concepts

NavigationLink

A NavigationLink represents a reusable destination.

Examples:

  • A Django named URL such as reports:index
  • A relative path such as /documentation/
  • An external HTTPS URL
  • A mailto: or tel: link
  • A context-aware detail URL resolved at render time

A link defines canonical information such as:

  • stable code
  • title and short title
  • description, tooltip, and ARIA label
  • destination and URL arguments
  • query parameters and fragment
  • target, rel, and download behavior
  • icon descriptor
  • enabled state and disabled reason
  • active-match strategy
  • visibility policy
  • search aliases
  • renderer-neutral metadata

A NavigationLink does not decide where it appears.

Launchpad

A Launchpad is a named, renderer-independent navigation composition.

Typical codes include:

  • primary_navigation
  • homepage
  • account_menu
  • mobile_navigation
  • report_actions
  • command_palette

Templates, tests, fixtures, and application code address a Launchpad by its stable code.

LaunchpadNode

A LaunchpadNode places something inside a Launchpad tree.

A node can be:

  • Link — a placement of a NavigationLink
  • Section — a structural grouping node
  • Separator — a structural divider

Nodes can be nested and ordered. A placement may override the linked destination's presentation without changing the canonical link.

Supported placement overrides include:

  • code
  • title and short title
  • description and tooltip
  • ARIA label
  • call-to-action label
  • icon
  • enabled state and disabled reason
  • visibility policy
  • metadata

Link-level visibility and node-level visibility are both enforced.

This means the link can define a global minimum policy, while an individual placement may restrict visibility further.


Resolution Pipeline

get_launchpad() resolves stored configuration into a ResolvedLaunchpad tree suitable for any renderer.

The reader:

  1. Normalizes the requested Launchpad code.
  2. Loads stable Launchpad configuration from the database or optional persistent cache.
  3. Loads active nodes and related navigation links efficiently.
  4. Builds or reuses the request-scoped visibility context.
  5. Applies node visibility.
  6. Applies linked NavigationLink visibility.
  7. Resolves context-aware URLs.
  8. Applies placement overrides.
  9. Builds the parent-child tree.
  10. Removes empty sections.
  11. Removes leading, trailing, and duplicate separators.
  12. Computes active state for links and their ancestors.
  13. Returns renderer-neutral data.

Missing or inactive Launchpads fail safely and return an empty ResolvedLaunchpad rather than raising during template rendering.

Reader Public API

The internal reader implementation is split into focused modules, while the public API remains intentionally small:

from launchpad.readers import (
    ResolvedLaunchpad,
    ResolvedNode,
    get_launchpad,
)

Cache snapshots, database loaders, tree construction, and resolution internals are implementation details and are not part of the supported public reader API.


Features

  • Renderer-independent navigation architecture
  • Reusable canonical destinations
  • Named Launchpad compositions
  • Arbitrarily nested node trees
  • Link, section, and separator nodes
  • Placement-specific presentation overrides
  • Public, authenticated, staff, superuser, and private audiences
  • Explicit user and group visibility
  • Django permission gates
  • all and any permission modes
  • Scheduled visibility windows
  • Registered runtime visibility rules
  • Named Django URLs and validated raw URLs
  • Positional and keyword URL arguments
  • Query parameters and fragments
  • Context-aware URL values
  • Active matching by path or Django view name
  • Disabled links that remain visible but non-navigable
  • Renderer-neutral metadata
  • Generic recursive tree renderer
  • Generic semantic card renderer
  • Optional responsive row-balancing helper for Bootstrap-style card grids
  • Optional persistent configuration caching
  • Generation-based cache invalidation
  • Configurable Django cache alias
  • Request-scoped visibility-context reuse
  • Transaction-safe cache invalidation
  • Django admin integration
  • Fully typed package with py.typed
  • Strict mypy validation
  • 100% statement and branch coverage

Installation

Install from PyPI:

python -m pip install django-launchpad

With uv:

uv add django-launchpad

Add Launchpad to INSTALLED_APPS:

INSTALLED_APPS = [
    # ...
    "launchpad.apps.LaunchpadConfig",
]

Run migrations:

python manage.py migrate

No package settings are required for normal operation.

Persistent configuration caching is optional and disabled by default.


Quick Start

Launchpad configuration can be created through Django admin, fixtures, migrations, the Django shell, or application code.

The following example creates one canonical destination and places it in a primary navigation Launchpad.

from launchpad.models import Launchpad, LaunchpadNode, NavigationLink


reports_link = NavigationLink.objects.create(
    code="reports",
    title="Reports",
    description="View operational reports.",
    url_type=NavigationLink.URLType.NAMED,
    url_value="reports:index",
    audience=NavigationLink.Audience.AUTHENTICATED,
)

primary_navigation = Launchpad.objects.create(
    code="primary_navigation",
    title="Primary Navigation",
)

LaunchpadNode.objects.create(
    launchpad=primary_navigation,
    kind=LaunchpadNode.Kind.LINK,
    navigation_link=reports_link,
    audience=LaunchpadNode.Audience.PUBLIC,
    sort_order=1000,
)

The node is public at the placement level, but the linked destination still requires an authenticated user. Both policies must pass.

Resolve in Python

from launchpad.readers import get_launchpad


navigation = get_launchpad(
    "primary_navigation",
    request=request,
)

The result is a ResolvedLaunchpad containing renderer-neutral ResolvedNode objects.

Resolve in a Template

{% load launchpad_tags %}

{% get_launchpad "primary_navigation" as navigation %}

Render the bundled generic tree:

{% include "launchpad/generic/tree.html" with launchpad=navigation only %}

Or render the same resolved Launchpad as semantic cards:

{% include "launchpad/generic/cards.html" with launchpad=navigation only %}

Or pass the same navigation object to a project-owned renderer.


Built-In Reference Renderers

Launchpad ships two lightweight reference renderers:

launchpad/generic/tree.html
launchpad/generic/cards.html

They are optional.

A consuming project may use either renderer directly, override them through normal Django template resolution, or ignore them completely and provide its own theme-specific templates.

Generic Tree Renderer

Use the tree renderer when the Launchpad should preserve its hierarchy:

{% load launchpad_tags %}
{% get_launchpad "primary_navigation" as navigation %}

{% include "launchpad/generic/tree.html" with launchpad=navigation only %}

The tree renderer supports nested sections, links, separators, icons, active and disabled state, accessibility attributes, recursive children, and empty Launchpads.

It emits neutral launchpad-* CSS hooks and does not ship CSS or JavaScript.

Generic Card Renderer

Use the card renderer when resolved navigation should be presented as a collection of semantic cards:

{% load launchpad_tags %}
{% get_launchpad "homepage" as navigation %}

{% include "launchpad/generic/cards.html" with launchpad=navigation only %}

The card renderer supports:

  • top-level links
  • link children contained in sections
  • icons
  • titles and descriptions
  • CTA labels
  • active and disabled state
  • target, rel, and download attributes
  • tooltip and ARIA metadata
  • empty Launchpads

Sections remain structural: their link children become cards rather than the section itself becoming a card.

Separators are intentionally ignored because they do not have a natural card representation.

The generic card renderer uses semantic classes such as:

launchpad-cards
launchpad-card
launchpad-card-header
launchpad-card-icon
launchpad-card-title
launchpad-card-description
launchpad-card-action
launchpad-card-link

It does not depend on Bootstrap, Tailwind CSS, Phoenix, or another theme.


Optional Configuration Caching

Launchpad configuration is naturally read-heavy and write-light.

A primary navigation tree may be resolved on nearly every request while the underlying Launchpad, nodes, links, user assignments, and group assignments change only occasionally.

django-launchpad therefore provides optional persistent caching for stable Launchpad configuration.

Caching is disabled by default.

Enable it explicitly:

LAUNCHPAD_CACHE_ENABLED = True

Default cache settings are:

LAUNCHPAD_CACHE_ENABLED = False
LAUNCHPAD_CACHE_ALIAS = "default"
LAUNCHPAD_CACHE_TIMEOUT = 900

All three settings are optional.

LAUNCHPAD_CACHE_ENABLED

Controls persistent configuration caching.

LAUNCHPAD_CACHE_ENABLED = True

Default:

False

When disabled, the normal ORM-backed reader path is used.

LAUNCHPAD_CACHE_ALIAS

Selects the Django cache alias Launchpad should use:

LAUNCHPAD_CACHE_ALIAS = "default"

A project may dedicate a separate cache:

CACHES = {
    "default": {
        # ...
    },
    "launchpad": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/2",
    },
}

LAUNCHPAD_CACHE_ENABLED = True
LAUNCHPAD_CACHE_ALIAS = "launchpad"

LAUNCHPAD_CACHE_TIMEOUT

Controls how long stable configuration snapshots remain physically stored:

LAUNCHPAD_CACHE_TIMEOUT = 900

The default is 15 minutes.

A timeout of 0 follows Django cache semantics and effectively prevents cached configuration values from being retained.

Negative values are rejected as configuration errors.

What Is Cached?

Persistent caching stores stable configuration snapshots containing data such as:

  • Launchpad identity and metadata
  • active Launchpad nodes
  • parent relationships
  • canonical NavigationLink configuration
  • placement overrides
  • visibility policy fields
  • explicit user IDs
  • explicit group IDs

Snapshots contain cache-safe data rather than live querysets.

What Is Not Cached?

ResolvedLaunchpad objects are not persistently cached.

User-, request-, time-, and context-sensitive behavior continues to run on every call, including:

  • current user visibility
  • scheduled visibility windows
  • context-aware URL references
  • current path
  • resolved Django view name
  • query-parameter matching
  • active-state calculation
  • ancestor active-state propagation

This boundary allows Launchpad to cache the expensive, stable configuration layer without freezing request-specific behavior.

Runtime Visibility Rules

Configurations containing registered runtime visibility rules deliberately stay on the live ORM path.

Runtime rules may depend on receiving real Django model instances through their obj argument. Launchpad therefore does not persist those configurations as cache snapshots.

This preserves the existing runtime-rule contract.

Request-Scoped Visibility Reuse

Persistent caching is only one part of reader efficiency.

During a single HTTP request, several Launchpads may be resolved:

primary_navigation
home
account_menu

Group membership and Django permission collection are user-derived and do not need to be rebuilt separately for every Launchpad.

Launchpad therefore memoizes the expensive base VisibilityContext on the request object and attaches a fresh runtime extra_context mapping to each read.

This is request-scoped memoization, not persistent caching.

Cache Invalidation

Launchpad uses generation-based invalidation.

Conceptually:

django-launchpad:f1:gABC:configuration:home

After a configuration change:

django-launchpad:f1:gXYZ:configuration:home

Existing entries become unreachable immediately and disappear naturally according to their cache timeout.

Launchpad does not depend on Redis-specific wildcard deletion or delete_pattern().

Automatic Invalidation

Configuration cache invalidation is triggered automatically after:

  • Launchpad save
  • Launchpad delete
  • NavigationLink save
  • NavigationLink delete
  • LaunchpadNode save
  • LaunchpadNode delete
  • NavigationLink.users add/remove/clear
  • NavigationLink.groups add/remove/clear
  • LaunchpadNode.users add/remove/clear
  • LaunchpadNode.groups add/remove/clear

Invalidation uses transaction.on_commit().

This means a new cache generation is not exposed until the database mutation that caused it has committed successfully.

A rolled-back transaction does not invalidate the cache.

Bulk Updates and Direct SQL

Operations that bypass Django model signals also bypass automatic invalidation.

Examples include:

NavigationLink.objects.filter(...).update(...)

and direct SQL.

In those cases, explicitly invalidate configuration:

from launchpad.cache import invalidate_configuration_cache


NavigationLink.objects.filter(...).update(active=False)

invalidate_configuration_cache()

Multi-Process Deployments

For multi-process production deployments, use a shared cache backend such as Redis or Memcached.

Django's local-memory cache is process-local. It is useful for development and testing, but it cannot provide cross-process cache sharing or invalidation.


Optional Responsive Row Balancing

Some responsive card grids look awkward when the real items wrap onto another row and leave that final row partially empty.

Launchpad provides an optional launchpad_pad template helper for this presentation concern.

The helper does not add fake nodes to the resolved Launchpad.

Instead, it returns either:

  • one render-only balancing placeholder descriptor, or
  • None when no breakpoint requires balancing.

Example:

{% load launchpad_tags %}
{% get_launchpad "home" as navigation %}

{% launchpad_pad navigation.nodes as padding %}

{% if padding %}
  <div class="{{ padding.classes }}">
    {{ padding.title }}
  </div>
{% endif %}

When Does Padding Appear?

Padding is needed only when both conditions are true at a responsive breakpoint:

  1. the real items have wrapped beyond the first row, and
  2. the final row is incomplete.

If all real items fit on one row, there is nothing to balance.

If the final row is already complete, there is nothing to balance.

The default responsive grid is:

xs: 1 column
sm: 2 columns
md: 3 columns
lg: 4 columns
xl: 6 columns

Three real items behave like:

xs → 1 / 1 / 1     no placeholder
sm → 2 / 1         placeholder fills the remaining slot
md → 3             no placeholder
lg → 3             no placeholder
xl → 3             no placeholder

Sixteen real items behave like:

xs → complete single-column rows         no placeholder
sm → complete two-column rows            no placeholder
md → 3 / 3 / 3 / 3 / 3 / 1             placeholder fills 2 slots
lg → 4 / 4 / 4 / 4                       no placeholder
xl → 6 / 6 / 4                           placeholder fills 2 slots

Responsive Width

The balancing placeholder expands across exactly the unused portion of the final row.

For a Bootstrap-style twelve-unit grid:

16 items at md / 3 columns
→ final row has 1 of 3 items
→ 2 columns are missing
→ placeholder spans 8 of 12 grid units

16 items at xl / 6 columns
→ final row has 4 of 6 items
→ 2 columns are missing
→ placeholder spans 4 of 12 grid units

Bootstrap-Compatible Helper

The current launchpad_pad helper emits Bootstrap-compatible responsive display and column classes such as:

d-none
d-sm-block
col-sm-6
d-md-none
d-lg-none
d-xl-none

This helper is optional.

The core Launchpad models, readers, visibility engine, cache, generic tree renderer, and generic card renderer do not require Bootstrap.

Projects using another frontend system may ignore launchpad_pad and implement balancing in their own renderer.

Placeholder Contract

The render-only placeholder exposes presentation values such as:

is_placeholder = True
title          = "More to Explore"
description    = "New tools, reports, and operational insights will appear here."
url            = "#"
enabled        = False
icon           = {"kind": "emoji", "value": "✨"}
classes        = "<responsive Bootstrap-compatible classes>"

A placeholder is:

  • not a NavigationLink
  • not a LaunchpadNode
  • not stored in the database
  • not part of persistent caching
  • not evaluated by the visibility engine
  • not part of the canonical navigation tree
  • not navigable

The original navigation.nodes collection is never modified.

Recommended Usage

The balancing helper is most useful inside a theme-specific card renderer that already knows its responsive grid:

{% for node in navigation.nodes %}
  {# render real cards #}
{% endfor %}

{% launchpad_pad navigation.nodes as padding %}

{% if padding %}
  <div class="{{ padding.classes }}">
    {# render the optional balancing card #}
  </div>
{% endif %}

Page templates can then remain simple:

{% load launchpad_tags %}
{% get_launchpad "home" as navigation %}

{% include "navigation/cards.html" with navigation=navigation only %}

Custom Rendering

Launchpad deliberately does not choose a visual framework.

The package does not require:

  • Bootstrap
  • Tailwind CSS
  • Phoenix
  • Bulma
  • Material UI
  • AdminLTE
  • JavaScript navigation libraries

The bundled generic renderers use neutral launchpad-* classes and provide reference implementations for tree and card presentations. They do not ship a theme, CSS, or JavaScript.

A consuming project may create any renderer it needs:

templates/
└── navigation/
    ├── sidebar.html
    ├── dashboard_cards.html
    ├── mobile_menu.html
    └── command_palette.html

Example:

{% load launchpad_tags %}

{% get_launchpad "homepage" as homepage_navigation %}

{% include "navigation/dashboard_cards.html" with launchpad=homepage_navigation only %}

Renderers receive resolved data, not ORM query responsibilities.

Resolved Node Data

A ResolvedNode exposes values such as:

  • kind
  • code
  • link_code
  • title
  • short_title
  • description
  • tooltip
  • aria_label
  • cta_label
  • url
  • target
  • rel
  • download
  • icon
  • enabled
  • disabled_reason
  • is_active
  • metadata
  • children

Convenience properties include:

  • is_link
  • is_section
  • is_separator
  • has_children

Visibility

Launchpad visibility controls whether a navigation object is shown.

It does not replace authorization in the destination view.

Destination views must continue to enforce their own permissions.

Audiences

Each visibility-aware link or node has a base audience:

  • public
  • authenticated
  • staff
  • superuser
  • private

These values express different policies.

For example, a destination that should be visible to every signed-in user should normally use:

link.audience = NavigationLink.Audience.AUTHENTICATED

A private object with no explicit users, groups, or permission grant is intentionally hidden from ordinary users.

Additional user, group, permission, schedule, and runtime-rule constraints may refine the base policy.

Explicit Users and Groups

Private navigation can be granted to selected users or groups:

link.users.add(user)
link.groups.add(group)

Permission gates still apply when configured.

Permission Gates

Permissions use standard Django permission strings:

link.permissions_required = [
    "reports.view_report",
    "reports.export_report",
]

Require every permission:

link.permissions_mode = NavigationLink.PermissionMode.ALL

Require any one permission:

link.permissions_mode = NavigationLink.PermissionMode.ANY

Permissions are gates, not optional alternate grants. An explicitly assigned user still fails visibility when a configured permission gate fails.

Permission-only visibility is supported:

link.audience = NavigationLink.Audience.PRIVATE
link.permissions_required = ["reports.view_report"]

In this configuration, the permission itself may grant visibility.

Scheduled Visibility

Use visible_from and visible_until to make links or placements visible only during a time window.

Inactive or scheduled-off objects remain hidden from everyone, including superusers.

Runtime Visibility Rules

Runtime rules support application-specific visibility that cannot be expressed through stored fields alone.

Register a rule under a safe code:

from launchpad.visibility import register_visibility_rule


@register_visibility_rule("has_reports_access")
def has_reports_access(*, obj, user, request, context) -> bool:
    return bool(
        user
        and user.is_authenticated
        and context.get("reports_enabled")
    )

Store only the rule code:

link.visibility_rule = "has_reports_access"

Launchpad never stores or imports arbitrary Python paths from the database.

Missing rules and rule exceptions fail closed and hide the object.


URL Resolution

Named Django URLs

link.url_type = NavigationLink.URLType.NAMED
link.url_value = "people:detail"
link.url_kwargs = {"pk": 42}

Raw URLs

Supported raw URL forms include:

  • relative paths
  • http
  • https
  • mailto
  • tel

Unsafe and unsupported values are rejected, including:

  • javascript:
  • data:
  • vbscript:
  • protocol-relative URLs
  • unsupported schemes
  • # as a disabled-link substitute

Use enabled=False for disabled navigation.

Query Parameters and Fragments

link.query_params = {
    "tab": "monthly",
    "tag": ["finance", "operations"],
}
link.fragment = "summary"

Existing query parameters are preserved.

Context-Aware Values

URL arguments, keyword arguments, and query parameters may resolve values at runtime.

Supported roots:

  • @user
  • @request
  • @context

Example:

link.url_type = NavigationLink.URLType.NAMED
link.url_value = "people:detail"
link.url_kwargs = {
    "pk": "@context.person.pk",
}
link.query_params = {
    "next": "@request.path",
    "viewer": "@user.username",
}

In a template:

{% get_launchpad "person_actions" person=person as navigation %}

Context traversal reads attributes and dictionary keys. Callables are not invoked. Missing values fail closed to an empty string.


Active Navigation

Links can determine whether they represent the current request.

Supported strategies:

  • auto
  • none
  • exact_path
  • path_prefix
  • view_name
  • view_prefix

auto prefers named-view matching for named URLs and then falls back to path matching.

Configured query parameters also participate in active-state matching.

When a descendant is active, its resolved ancestors are marked active as well, allowing renderers to expand the appropriate sections.


Disabled Navigation

Disabled destinations may remain visible while becoming non-navigable.

link.enabled = False
link.disabled_reason = "Coming soon."

A placement can override the canonical enabled state:

node.enabled_override = False
node.disabled_reason_override = "Unavailable in this workspace."

Disabled nodes resolve to #, and the generic renderers emit non-clickable markup with aria-disabled="true".


Icons

Launchpad stores renderer-neutral icon descriptors.

Supported descriptors:

  • none
  • emoji
  • fa
  • fe

Example:

link.icon_type = NavigationLink.IconType.EMOJI
link.emoji = "📊"

Or:

link.icon_type = NavigationLink.IconType.FA
link.icon_class = "fa-solid fa-chart-line"

The package does not bundle Font Awesome, Feather, or any other icon library. Renderers decide how descriptors are presented.


Django Admin

Launchpad registers all three models with Django admin:

  • NavigationLink
  • Launchpad
  • LaunchpadNode

The admin provides:

  • structured fieldsets
  • search and filtering
  • related-object autocomplete
  • inline node editing inside a Launchpad
  • direct node editing for larger trees
  • resolved-value inspection
  • automatic created_by assignment
  • optimized changelist querysets

Django admin is a management interface, not a rendering requirement.


Template Tags

Templates load the complete Launchpad tag library with:

{% load launchpad_tags %}

The tag implementation is internally split into focused modules, but the template-facing API remains a single library.

get_launchpad

Resolve a Launchpad:

{% get_launchpad "primary_navigation" as navigation %}

Supply an explicit user when needed:

{% get_launchpad "primary_navigation" user=request.user as navigation %}

Pass runtime context values:

{% get_launchpad "person_actions" person=person as navigation %}

launchpad_pad

Build an optional responsive balancing descriptor:

{% launchpad_pad navigation.nodes as padding %}

The helper never changes the resolved Launchpad or its node collection.


Public Python API

Models:

from launchpad.models import Launchpad, LaunchpadNode, NavigationLink

Reader:

from launchpad.readers import (
    ResolvedLaunchpad,
    ResolvedNode,
    get_launchpad,
)

Visibility:

from launchpad.visibility import (
    VisibilityContext,
    build_user_context,
    get_visibility_rule,
    is_visible,
    register_visibility_rule,
)

Cache invalidation for signal-bypassing bulk operations:

from launchpad.cache import invalidate_configuration_cache

Template usage:

{% load launchpad_tags %}

{% get_launchpad "primary_navigation" as navigation %}
{% launchpad_pad navigation.nodes as padding %}

What Launchpad Does Not Do

Launchpad intentionally does not:

  • replace authorization in Django views
  • generate project views or URL patterns
  • require a frontend framework
  • require Bootstrap
  • ship an opinionated visual theme
  • bundle CSS or JavaScript
  • bundle icon libraries
  • store executable Python import paths
  • force navigation definitions into code or templates
  • assume a specific user model
  • require django-allauth
  • require Django REST Framework
  • require Redis
  • require persistent caching
  • persist presentation placeholders

The optional responsive padding helper emits Bootstrap-compatible classes, but using that helper is not required.

It is a focused Django application for navigation data, policy, resolution, composition, optional configuration caching, and lightweight reference rendering.


Custom User Models and django-allauth

User relationships use settings.AUTH_USER_MODEL.

Launchpad works with:

  • Django's built-in user model
  • custom AbstractUser models
  • custom AbstractBaseUser models
  • django-allauth with the project's configured user model

The visibility engine relies only on Django's standard authentication interface, including:

  • is_authenticated
  • is_staff
  • is_superuser
  • groups
  • get_all_permissions()

Supported Versions

Python Django 5.2 Django 6.0
3.11 Yes No
3.12 Yes Yes
3.13 Yes Yes
3.14 Yes Yes

Package metadata currently allows:

Python >= 3.11
Django >= 5.2, < 6.1

Quality

django-launchpad is developed with the same quality standards used across FIFOA Labs packages.

  • Ruff formatting and linting
  • Strict mypy validation across source and tests
  • django-stubs
  • Pytest and pytest-django
  • 100% statement coverage
  • 100% branch coverage
  • CI across supported Python and Django combinations
  • Django system checks
  • Migration drift checks
  • Migration application smoke testing
  • Source and wheel distribution validation
  • Required wheel-content validation
  • Clean-wheel installation testing
  • Typed distribution via py.typed
  • PyPI Trusted Publishing

Project Status

django-launchpad is suitable for integration and real-world use, but its public API remains pre-1.0 and may continue to evolve as the package is adopted by additional Django projects.

Recent development has added:

  • optional configuration caching
  • request-scoped visibility reuse
  • automatic transaction-safe cache invalidation
  • modular reader internals
  • split template-tag internals behind the same {% load launchpad_tags %} API
  • optional responsive row balancing
  • a generic semantic card renderer alongside the existing generic tree renderer

Semantic versioning is used:

  • patch releases fix bugs and documentation and may refine existing behavior
  • minor 0.x releases may add features or refine pre-1.0 APIs
  • 1.0.0 will mark a stable public compatibility commitment

See the PyPI badge and CHANGELOG.md for the currently released version.


Contributing

Issues and pull requests are welcome.

Before submitting changes, run:

make check
make coverage
make build
make check-dist
make install-wheel

For the full local release validation pipeline:

make release-check

See CONTRIBUTING.md for project guidelines.


License

django-launchpad is released under the MIT License.

See LICENSE for the full license text.


Built and maintained by FIFOA Labs.

Metadata

Release files for django-launchpad 0.2.1

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

Source distribution (sdist)

Source distribution for django-launchpad 0.2.1
File Size Uploaded
django_launchpad-0.2.1.tar.gz 58.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-launchpad 0.2.1
File Interpreter ABI Platform
django_launchpad-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 121.7 kB

Release files / django_launchpad-0.2.1.tar.gz

Download URL django_launchpad-0.2.1.tar.gz
Size 58.5 kB
Tags Source
SHA-256 checksum
How to use checksums
3b9c578657912015dadad25dc94ad968cd3d9b598d38caabf7bc4c2b63d7b84f
BLAKE2b-256 checksum
How to use checksums
64fa15f35be6ff6299ed54c5a5730f583a6ee5dec0dcb7feb02ed969ba170c17
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 12, 2026.

Transparency log

Release files / django_launchpad-0.2.1-py3-none-any.whl

Download URL django_launchpad-0.2.1-py3-none-any.whl
Size 63.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
07f7c36f3c82d1b15368905ab6e518626a70c8912ce5930d6527dd2147a9259f
BLAKE2b-256 checksum
How to use checksums
5817224d9e17a62bc4c9d4a5137dacd88af0122f5364ee8812dfe2649acfe69d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.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