Add Happy 404, featured/related sidebars, and next-post nav to the post page
Happy 404: an HTML 404 (unmatched route, or an explicit not-found inside a
real route) now renders a random existing post instead of the error page,
using the exact same context builder as a real post view. Toggle is the
happy_404_enabled site setting (default on, /admin/settings); JSON/API
requests and a handful of excluded prefixes are never affected. The pool of
candidate slugs is cached in-process and resampled periodically so it stays
fast and eventually cycles the whole posts table; on any internal failure it
falls straight through to the real 404 page.
Applying this everywhere surfaced ~60 existing tests that asserted a literal
404 for a legitimate resource-not-found flow (deleted post, unknown
container, wrong project slug, etc.) - each now disables the setting for the
duration of that specific check and restores it after, so the underlying
not-found behavior stays covered independently of the new feature.
Post page also gained, all built on the same shared post_page_context() so
they render identically on both a real post and a happy-404 page:
- A left sidebar (three separate cards, matching /feed's sidebar-card
convention) for "Gists from {author}", "Projects from {author}" (private
projects filtered through the normal visibility check), and "Related
Discussions" - each cached per author and invalidated on create/edit/
delete so new content shows up immediately.
- A right column reusing /feed's exact Daily Topic widget class for up to
three "Featured" articles (the existing but previously-unused `featured`
news flag), cached as a pool with per-request random sampling.
- A "Next post -> " link beside "Back to Feed", pointing at the next older
post site-wide (blocked authors skipped). Wired through the same next_url
mechanism already used for listing pagination, so it emits a real
backend-rendered <link rel="next"> tag for SEO, not just a visible link.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWJy6PrMMt5hwWxQwia2rd
This commit is contained in:
@@ -288,6 +288,7 @@ Operational behavior is tunable live from `/admin/settings` (stored in `site_set
|
||||
| `registration_open` | `1` | When `0`, new sign-ups are rejected |
|
||||
| `maintenance_mode` | `0` | When `1`, non-admins see the maintenance page; admins retain access |
|
||||
| `maintenance_message` | scheduled-maintenance text | Message shown during maintenance |
|
||||
| `happy_404_enabled` | `1` | When `1`, an HTML page that would otherwise 404 renders a random existing post instead ("Happy 404"); JSON/API requests always get a real 404 regardless. See [Happy 404](#happy-404) |
|
||||
| `customization_enabled` | `1` | When `0`, no user CSS/JS customization is injected on any page |
|
||||
| `customization_js_enabled` | `1` | When `0`, user custom CSS is still served but custom JavaScript is suppressed |
|
||||
| `audit_log_retention_days` | `90` | Audit rows older than this are pruned daily by the Audit retention service; `0` disables pruning |
|
||||
@@ -342,6 +343,27 @@ curl -H "Accept: application/json" https://your-host/feed
|
||||
curl -H "Accept: application/json" -X POST -d "content=hi&title=T&topic=devlog" https://your-host/posts/create
|
||||
```
|
||||
|
||||
## Happy 404
|
||||
|
||||
Instead of a bare error page, an HTML request that would 404 (an unmatched route, or an app route
|
||||
raising `not_found()` for a missing resource) instead renders a random existing post at that URL,
|
||||
using the exact same template and context as the real `/posts/{slug}` page. JSON/API requests are
|
||||
unaffected and still get a normal `404` - the substitution only ever applies to a browser HTML
|
||||
navigation.
|
||||
|
||||
- **Toggle:** `happy_404_enabled` site setting (`/admin/settings`, on by default).
|
||||
- **Scope:** any HTML `GET` 404, app-wide - not just under `/posts`.
|
||||
- **Performance:** a small pool of random post slugs is cached in-process for a few minutes and
|
||||
refreshed with a fresh random sample on expiry, so every request only does an in-memory pick plus
|
||||
one indexed lookup - no per-request full-table scan - while the pool composition still cycles
|
||||
through the whole `posts` table over time.
|
||||
- **SEO safety:** the substituted page is always marked `noindex,nofollow` so the decoy URL is never
|
||||
indexed under the wrong address.
|
||||
- **Fail-closed:** any error while building the substitute page falls straight through to the normal
|
||||
404 page - this feature can never turn a real error into a worse one.
|
||||
|
||||
Implementation: `devplacepy/happy404.py`, wired into the `404` exception handler in `main.py`.
|
||||
|
||||
## XML-RPC bridge
|
||||
|
||||
The full REST API is also reachable over XML-RPC at `/xmlrpc`. A standalone forking XML-RPC
|
||||
|
||||
Reference in New Issue
Block a user