blog2

Allotment and Gardening AllManiac

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.

Requirements

Local setup

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.

Turso/libSQL

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.

Submissions, moderation, and email

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.

Import and export

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.

Media storage

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.

Render deployment

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.

Verification

python manage.py check
python manage.py test