A server-rendered Django recreation of the public AllManiac gardening blog. Visitors can read approved posts, submit posts and comments for staff review, and like published posts and comments. Staff sign in to Django admin to approve, reject, or delete submissions. The existing sunset background image is retained.
From the project directory, activate the existing environment and install the requirements:
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
Copy-Item .env.example .env
The app loads settings from environment variables; Django does not read .env
automatically. Set the values in your terminal, or use your preferred local
environment loader. With both Turso variables blank, the app uses SQLite at
db.sqlite3 for development and tests.
$env:DJANGO_DEBUG = "true"
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
Visit http://127.0.0.1:8000/. The public site has no visitor sign-in. Staff
use http://127.0.0.1:8000/admin/; create staff credentials with
createsuperuser. Never expose the development server to the public internet.
The Django backend uses turso-serverless to connect over HTTPS, which works
with Turso databases hosted on AWS (WebSocket connections are not supported
there). Set both variables to configure it:
TURSO_DATABASE_URL=libsql://your-database-name.turso.io
TURSO_AUTH_TOKEN=your-token
Both settings are required together. If either is missing, startup fails with
an actionable configuration error; with neither set, local SQLite is selected.
The backend receives the URL and token separately from the environment;
credentials are not committed or embedded in the database URL. Run
python manage.py migrate after configuring Turso. The automated test suite
runs against a temporary SQLite database and does not need Turso.
New posts and comments start in pending state and are never shown publicly
until approved. In Django admin, select one or more items and choose an
Approve or Reject action; the standard delete action removes them.
Email is sent to configured moderator addresses for new submissions and to
submitters after an approve/reject decision when they supplied an email.
Configure SMTP with DJANGO_EMAIL_BACKEND,
DJANGO_EMAIL_HOST, DJANGO_EMAIL_PORT, DJANGO_EMAIL_USE_TLS,
DJANGO_EMAIL_HOST_USER, DJANGO_EMAIL_HOST_PASSWORD, and
DJANGO_DEFAULT_FROM_EMAIL. Set MODERATION_NOTIFICATION_EMAILS to a
comma-separated list. Local development defaults to Django’s console email
backend. Do not put real email credentials in source control.
The visit count tracks one visit per browser session per local calendar day. Only a hash of a random session token is stored; client IP addresses are not collected. Likes are likewise unique per browser session.
The commands support a JSON array or {"posts": [...]} using title and
body (or content) fields:
python manage.py import_posts --file posts.json
python manage.py export_posts --file posts-export.json
Imports are approved by default (use --status pending to hold them for
review). Exports include approved posts by default; a staff operator should
only use --include-unapproved when intentionally exporting private pending or
rejected submissions. Uploaded image files are not embedded in JSON.
To copy the public, approved posts, comments, and images from the original AllManiac blog into the configured database, run:
python manage.py import_legacy_blog --dry-run
python manage.py import_legacy_blog
The import is safe to rerun: source IDs prevent duplicate posts and comments, and images are stored with their posts in the database rather than Render’s ephemeral filesystem.
Uploads use local MEDIA_ROOT (default media/) unless
MEDIA_STORAGE_BACKEND=s3 selects the optional S3-compatible Django storage
backend. Configure AWS_STORAGE_BUCKET_NAME, optionally AWS_S3_REGION_NAME
and AWS_S3_ENDPOINT_URL, and provide credentials through the standard AWS
environment variables. This does not require Azure Blob Storage.
The root render.yaml defines a free Render web service. Connect this
repository in the Render dashboard as a Blueprint and set the requested
TURSO_DATABASE_URL and TURSO_AUTH_TOKEN environment variables there; the
Django secret is generated by Render. Set DJANGO_ALLOWED_HOSTS to the
service’s actual onrender.com hostname; the Render-provided hostname is also
added automatically. Render checks /healthz/, a lightweight endpoint that
does not query Turso. The blueprint pins Python 3.13 because
the Django libSQL backend imports a sqlite3 attribute removed in Python
3.14. It also accepts GMAIL_USER, GMAIL_APP_PASSWORD, and
NOTIFY_EMAIL for Gmail notifications. The service runs migrations at startup
and serves collected static files with WhiteNoise. Production mode refuses to
start without both Turso settings, so it will not silently store production
data in the service’s temporary local filesystem.
The free service may spin down after inactivity. Uploaded images use local media storage by default, which is not persistent across service restarts or deploys; configure an S3-compatible media backend before relying on uploaded images in production.
For production, serve uploaded media via the configured storage and serve
static files with a production static-file server. Set DJANGO_DEBUG=false,
provide a strong DJANGO_SECRET_KEY, configure DJANGO_ALLOWED_HOSTS, and
use HTTPS and secure deployment settings before public hosting.
python manage.py check
python manage.py test