Skip to main content

simple_module_pagebuilder

Drag-and-drop visual page builder for simple_module apps. Pages are composed from a reusable block library using Puck and stored as JSON; published pages are served at /p/{slug}.

Features

  • Visual editor — Puck-based drag-and-drop with a block library (heading, text, image, button, columns, spacer) and a media picker.
  • Site layout — a shared header/footer layout, edited the same way, wrapped around every public page.
  • Media library — uploads with magic-byte content sniffing, folder organisation, and server-generated WebP thumbnails.
  • Revisions — every status transition snapshots the page, with a diff view and restore-as-draft.
  • Approval workflow — edit → submit for review → approve / reject, gated by three separate permissions.
  • Scheduled publishing — set publish_at / unpublish_at; a background poller flips status at the due time.
  • SEO — per-page meta description, canonical URL, OG image, JSON-LD, plus /sitemap.xml and /robots.txt.

Installation

uv add simple_module_pagebuilder

Then add it to your host's pyproject.toml dependencies. The module is discovered at boot through the simple_module entry point — no registration code required.

Its frontend ships inside the wheel. Pull its JS dependencies into your host's client app and regenerate the page manifest with:

smpy host sync-js-deps --host-client-app=host/client_app
smpy host gen-pages --host-dir=host/client_app

Usage

Once installed and migrated, sign in and open /pagebuilder:

  1. Create a page/pagebuilder/new. Give it a title; the slug is derived from it and stays editable.
  2. Compose it — drag blocks from the Puck sidebar. Images come from the media library, which uploads and thumbnails them for you.
  3. Publish it — publish directly with pagebuilder.publish, or submit for review if your role only carries pagebuilder.edit. Reviewers work through /pagebuilder/pending.
  4. Schedule it — set publish_at / unpublish_at instead of publishing now, and the poller flips the status at the due time.
  5. View it — the page is live at /p/{slug}, wrapped in the site layout from /pagebuilder/layout.

Every transition writes a revision, so /pagebuilder/{id}/edit can compare any two revisions and restore either one as a draft.

Contributing blocks from another module

A module ships Puck blocks by calling registerPuckBlocks from its puck-blocks.ts; the host imports every module's registration eagerly at app start, so the registry is populated before the first render.

Because registration is eager, a block that statically imports a heavy render component puts that component's whole dependency graph in the entry chunk — on every page of the site, for every visitor. Measured on a consuming site, a map block's static import put maplibre-gl (1 MB minified, 62% of the bundle) in front of visitors who never opened the map. Register heavy blocks with lazyBlock instead, which keeps fields and defaults eager but loads the component the first time a page actually renders the block:

import { lazyBlock } from '@simple-module-py/pagebuilder/pagebuilder/components/lazyBlock';
import { registerPuckBlocks } from '@simple-module-py/pagebuilder/pagebuilder/components/blockRegistry';

registerPuckBlocks({
  blocks: {
    AtlasMap: lazyBlock(
      () => import('./components/AtlasMap').then((m) => m.AtlasMapEmbed),
      { label: 'Atlas map', fields: { /* … */ }, defaultProps: { /* … */ } },
      (props) => <div className={heightClass[props.height]} aria-busy="true" />,
    ),
  },
});

Light blocks (text, cards, lists) can keep their static imports — the split only pays for itself when the component drags in something big.

Migrations

This module ships no migrations — that is the framework convention. Its SQLModel tables are picked up by your host's build_module_metadata(), so after installing it run:

make migration msg="add pagebuilder"
make migrate

Add branch_labels = ("pagebuilder",) to that first generated revision so the module can later be removed on its own with alembic downgrade pagebuilder@base.

Permissions

Permission Grants
pagebuilder.edit Draft, save, submit for review, restore a revision
pagebuilder.publish Publish and unpublish directly
pagebuilder.approve Approve or reject a submitted page

DEFAULT_ROLE_MAP in pagebuilder.permissions suggests three roles — pagebuilder_editor, pagebuilder_publisher, pagebuilder_approver — cumulatively mapped to those permissions.

Settings

All settings use the SM_PAGEBUILDER_ env prefix.

Setting Default Purpose
public_route_prefix /p Where published pages are served
requires_auth true Gate the admin surface behind authentication
csrf_protect true Require a CSRF token on mutating admin requests
media_root var/pagebuilder/media Upload storage directory
media_url_prefix /media/pagebuilder Public URL prefix for uploads
media_max_bytes 10485760 Per-upload size ceiling
media_thumbnail_widths 320,640,1280,1920 Widths generated as WebP
media_webp_quality 82 WebP encoder quality
public_csp see settings.py CSP header on public pages
public_cache_max_age 60 max-age on public pages
public_base_url None Absolute base for canonical URLs and the sitemap
site_name None OpenGraph site name
sitemap_enabled true Serve /sitemap.xml
robots_enabled true Serve /robots.txt
scheduler_enabled true Run the scheduled publish/unpublish poller
scheduler_interval_seconds 30 Poll interval

Routes

Admin views (under /pagebuilder): /, /new, /{page_id}/edit, /pending, /layout, /media.

Admin API (under /api/pagebuilder): pages CRUD, workflow transitions, revisions and diffs, layout and its revisions, uploads.

Public: /p/{slug}, /sitemap.xml, /robots.txt.

Contracts

DTOs other modules may depend on are exported from pagebuilder.contracts. Everything else in the package is internal and may change without a major version bump.

Licence

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simple_module_pagebuilder-0.0.6.tar.gz (273.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simple_module_pagebuilder-0.0.6-py3-none-any.whl (368.8 kB view details)

Uploaded Python 3

File details

Details for the file simple_module_pagebuilder-0.0.6.tar.gz.

File metadata

File hashes

Hashes for simple_module_pagebuilder-0.0.6.tar.gz
Algorithm Hash digest
SHA256 5d0e3a3c9a49127cc9705c460c788fd6119ffab6a57f3c63ba8e869add7bad1b
MD5 3fe013ae07b19fb4652cf8d2b7a7eb5d
BLAKE2b-256 7b50a7e2340aed61f22b48c0b58d4c21c15980578bd37864d4fd53fe0b45b1e6

See more details on using hashes here.

Provenance

The following attestation bundles were made for simple_module_pagebuilder-0.0.6.tar.gz:

Publisher: release.yml on antosubash/smpy_modules

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file simple_module_pagebuilder-0.0.6-py3-none-any.whl.

File metadata

File hashes

Hashes for simple_module_pagebuilder-0.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 b5f7c0c5a16bc4ba686c4b98f9aa1c5466c6113e7538659e1c0638ddfe2e169b
MD5 a0eb216a2b431b29c8609b29be945cac
BLAKE2b-256 6568be952ddc1a87b2c9f87dcf5e28981b58ea9206c5da1d7537c16e24b945ca

See more details on using hashes here.

Provenance

The following attestation bundles were made for simple_module_pagebuilder-0.0.6-py3-none-any.whl:

Publisher: release.yml on antosubash/smpy_modules

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.0.6 This release

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 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