mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 10:31:08 -05:00
- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols. - Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage. - Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
ThrillWiki Template System
This document describes the template architecture, conventions, and best practices for ThrillWiki.
Directory Structure
templates/
├── base/ # Base templates
│ └── base.html # Root template all pages extend
├── components/ # Reusable UI components
│ ├── ui/ # UI primitives (button, card, toast)
│ ├── modals/ # Modal components
│ ├── pagination.html # Pagination (supports HTMX)
│ ├── status_badge.html # Status badge (parks/rides)
│ ├── stats_card.html # Statistics card
│ └── history_panel.html # History/audit trail
├── forms/ # Form-related templates
│ ├── partials/ # Form field components
│ │ ├── form_field.html
│ │ ├── field_error.html
│ │ └── field_success.html
│ └── layouts/ # Form layout templates
│ ├── stacked.html # Vertical layout
│ ├── inline.html # Horizontal layout
│ └── grid.html # Multi-column grid
├── htmx/ # HTMX-specific templates
│ ├── components/ # HTMX components
│ └── README.md # HTMX documentation
├── {app}/ # App-specific templates
│ ├── {model}_list.html # List views
│ ├── {model}_detail.html # Detail views
│ ├── {model}_form.html # Form views
│ └── partials/ # App-specific partials
└── README.md # This file
Template Inheritance
Base Template
All pages extend base/base.html. Available blocks:
{% extends "base/base.html" %}
{# Page title (appears in <title> and meta tags) #}
{% block title %}My Page - ThrillWiki{% endblock %}
{# Main page content #}
{% block content %}
<h1>Page Content</h1>
{% endblock %}
{# Additional CSS/meta in <head> #}
{% block extra_head %}
<link rel="stylesheet" href="...">
{% endblock %}
{# Additional JavaScript before </body> #}
{% block extra_js %}
<script src="..."></script>
{% endblock %}
{# Additional body classes #}
{% block body_class %}custom-page{% endblock %}
{# Additional main element classes #}
{% block main_class %}no-padding{% endblock %}
{# Override navigation (defaults to enhanced_header.html) #}
{% block navigation %}{% endblock %}
{# Override footer #}
{% block footer %}{% endblock %}
{# Meta tags for SEO #}
{% block meta_description %}Page description{% endblock %}
{% block og_title %}Open Graph title{% endblock %}
{% block og_description %}Open Graph description{% endblock %}
Inheritance Example
{% extends "base/base.html" %}
{% load static %}
{% load park_tags %}
{% block title %}{{ park.name }} - ThrillWiki{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="{% static 'css/park-detail.css' %}">
{% endblock %}
{% block content %}
<div class="container">
<h1>{{ park.name }}</h1>
{% include "parks/partials/park_header_badge.html" %}
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'js/park-map.js' %}"></script>
{% endblock %}
Component Usage
Pagination
{# Standard pagination #}
{% include 'components/pagination.html' with page_obj=page_obj %}
{# HTMX-enabled pagination #}
{% include 'components/pagination.html' with page_obj=page_obj use_htmx=True hx_target='#results' %}
{# Small size #}
{% include 'components/pagination.html' with page_obj=page_obj size='sm' %}
Status Badge
{# Basic badge #}
{% include 'components/status_badge.html' with status=park.status %}
{# Interactive badge with HTMX refresh #}
{% include 'components/status_badge.html' with
status=park.status
badge_id='park-header-badge'
refresh_trigger='park-status-changed'
scroll_target='park-status-section'
can_edit=perms.parks.change_park
%}
Stats Card
{# Basic stat #}
{% include 'components/stats_card.html' with label='Total Rides' value=park.ride_count %}
{# Clickable stat #}
{% include 'components/stats_card.html' with label='Total Rides' value=42 link=rides_url %}
{# Priority stat (highlighted) #}
{% include 'components/stats_card.html' with label='Operator' value=park.operator.name priority=True %}
History Panel
{# Basic history #}
{% include 'components/history_panel.html' with history=history %}
{# With FSM toggle for moderators #}
{% include 'components/history_panel.html' with
history=history
show_fsm_toggle=True
fsm_history_url=fsm_url
model_type='park'
object_id=park.id
can_view_fsm=perms.parks.change_park
%}
Loading Indicator
{# Block indicator #}
{% include 'htmx/components/loading_indicator.html' with id='loading' message='Loading...' %}
{# Inline indicator (in buttons) #}
{% include 'htmx/components/loading_indicator.html' with id='btn-loading' inline=True size='sm' %}
{# Overlay indicator #}
{% include 'htmx/components/loading_indicator.html' with id='overlay' mode='overlay' %}
Form Rendering
Form Layouts
{# Stacked layout (default) #}
{% include 'forms/layouts/stacked.html' with form=form %}
{# Inline/horizontal layout #}
{% include 'forms/layouts/inline.html' with form=form %}
{# 2-column grid #}
{% include 'forms/layouts/grid.html' with form=form cols=2 %}
{# With excluded fields #}
{% include 'forms/layouts/stacked.html' with form=form exclude='password2' %}
{# Custom submit text #}
{% include 'forms/layouts/stacked.html' with form=form submit_text='Save Changes' %}
Individual Fields
{# Standard field #}
{% include 'forms/partials/form_field.html' with field=form.email %}
{# Field with custom label #}
{% include 'forms/partials/form_field.html' with field=form.email label='Email Address' %}
{# Field with HTMX validation #}
{% include 'forms/partials/form_field.html' with
field=form.username
hx_validate=True
hx_validate_url='/api/validate/username/'
%}
Template Tags
Loading Order
Always load template tags in this order:
{% load static %}
{% load i18n %}
{% load park_tags %} {# App-specific #}
{% load safe_html %} {# Sanitization #}
{% load common_filters %} {# Utility filters #}
{% load cache %} {# Caching (if used) #}
Available Filters
common_filters:
{{ datetime|humanize_timedelta }} {# "2 hours ago" #}
{{ text|truncate_smart:50 }} {# Truncate at word boundary #}
{{ number|format_number }} {# "1,234,567" #}
{{ number|format_compact }} {# "1.2K", "3.4M" #}
{{ dict|get_item:"key" }} {# Safe dict access #}
{{ count|pluralize_custom:"item,items" }}
{{ field|add_class:"form-control" }}
safe_html:
{{ content|sanitize }} {# Full HTML sanitization #}
{{ comment|sanitize_minimal }} {# Basic text only #}
{{ text|strip_html }} {# Remove all HTML #}
{{ data|json_safe }} {# Safe JSON for JS #}
{% icon "check" class="w-4 h-4" %} {# SVG icon #}
Context Variables
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Single object | Lowercase model name | park, ride, user |
| List/QuerySet | {model}_list |
park_list, ride_list |
| Paginated | page_obj |
Django standard |
| Single form | form |
Standard |
| Multiple forms | {purpose}_form |
login_form, signup_form |
Avoid
- Generic names:
object,item,obj,data - Abbreviated names:
p,r,f,frm
HTMX Patterns
See htmx/README.md for detailed HTMX documentation.
Quick Reference
{# Swap strategies #}
hx-swap="innerHTML" {# Replace content inside #}
hx-swap="outerHTML" {# Replace entire element #}
hx-swap="beforeend" {# Append #}
hx-swap="afterbegin" {# Prepend #}
{# Target naming #}
hx-target="#park-123" {# Specific object #}
hx-target="#results" {# Page section #}
hx-target="this" {# Self #}
{# Event naming #}
hx-trigger="park-status-changed from:body"
hx-trigger="auth-changed from:body"
Caching
Use fragment caching for expensive template sections:
{% load cache %}
{# Cache navigation for 5 minutes per user #}
{% cache 300 nav user.id %}
{% include 'components/layout/enhanced_header.html' %}
{% endcache %}
{# Cache stats with object version #}
{% cache 600 park_stats park.id park.updated_at %}
{% include 'parks/partials/park_stats.html' %}
{% endcache %}
Cache Keys
Include in cache key:
- User ID for personalized content
- Object ID for object-specific content
updated_atfor automatic invalidation
Do NOT cache:
- User-specific actions (edit buttons)
- Form CSRF tokens
- Real-time data
Accessibility
Checklist
- Single
<h1>per page - Heading hierarchy (h1 → h2 → h3)
- Labels for all form inputs
aria-labelfor icon-only buttonsaria-describedbyfor help textaria-invalidfor fields with errorsrole="alert"for error messagesaria-livefor dynamic content
Skip Links
Base template includes skip link to main content:
<a href="#main-content" class="sr-only focus:not-sr-only ...">
Skip to main content
</a>
Landmarks
<nav role="navigation" aria-label="Main navigation">
<main role="main" aria-label="Main content">
<footer role="contentinfo">
Security
Safe HTML Rendering
{# User content - ALWAYS sanitize #}
{{ user_description|sanitize }}
{# Comments - minimal formatting #}
{{ comment_text|sanitize_minimal }}
{# Remove all HTML #}
{{ raw_text|strip_html }}
{# NEVER use |safe for user content #}
{{ user_input|safe }} {# DANGEROUS! #}
JSON in Templates
{# Safe JSON for JavaScript #}
<script>
const data = {{ python_dict|json_safe }};
</script>
Component Documentation Template
Each component should have a header comment:
{% comment %}
Component Name
==============
Brief description of what the component does.
Purpose:
Detailed explanation of the component's purpose.
Usage Examples:
{% include 'components/example.html' with param='value' %}
Parameters:
- param1: Description (required/optional, default: value)
- param2: Description
Dependencies:
- Alpine.js for interactivity
- HTMX for dynamic updates
Security: Notes about sanitization, XSS prevention
{% endcomment %}
File Naming
| Type | Pattern | Example |
|---|---|---|
| List view | {model}_list.html |
park_list.html |
| Detail view | {model}_detail.html |
park_detail.html |
| Form view | {model}_form.html |
park_form.html |
| Partial | {model}_{purpose}.html |
park_header_badge.html |
| Component | {purpose}.html |
pagination.html |