Styling & Templates
Personal Catalogue: Django & PostgreSQL
Chapter 8 · Styling & Templates
The functional core is done: real schema, a real admin, real public list/detail pages, a real add-item form, tag filtering, and search. This chapter deliberately comes after all of that, not before — matching this project's own real deadline-driven priority order from Chapter 1, function before polish.
Django's Static Files System
CSS, images, and any other static asset live in a dedicated app-level directory, wired up through a small, one-time settings change:
Create catalogue/static/catalogue/style.css — the extra catalogue/ nesting inside static/ is a real, deliberate Django convention (namespacing static files by app), avoiding a real file-name collision if a second app in this project ever also defines a style.css.
A Real Base Template With Actual Styling
{% load static %} must appear before any {% static %} tag is used in the template — a real, easy-to-forget requirement, since forgetting it produces a confusing TemplateSyntaxError pointing at the {% static %} line itself rather than at the missing {% load %} that actually caused it.
A Reusable Item Card Partial
Both the list page and the tag-filtered list page render items the same way — a real, genuine case for Django's own template composition, rather than maintaining the same markup twice:
And in item_list.html:
_item_card.html is a real, common Django community convention — not a Django feature itself — marking a template as a partial, meant to be included rather than rendered directly as a page. Pulling this markup into its own file means a future styling change to how an item card looks only needs to happen in one place, rather than being kept in sync across the list page and anywhere else an item might eventually be rendered the same way.
Real Dark-Theme Styling
STATICFILES_DIRS is where Django's own development server looks for static files while DEBUG = True — genuinely convenient locally, but Django's development server is explicitly documented as unsuitable for serving static files in production. A real production deployment instead runs python manage.py collectstatic, which copies every static file from every app into one single folder — STATIC_ROOT, a separate setting not yet defined in this project — for a real web server (or a service like WhiteNoise) to serve directly. This distinction doesn't matter yet, but it matters directly once Chapter 9 covers real deployment.
Trying It Out
Reload the list page and confirm the nav bar, dark theme, and pill-styled tags all render correctly, and that clicking a tag pill still filters exactly as it did before this chapter — styling changed nothing about the underlying filtering logic from Chapter 6.
Hands-On Exercises
Build style.css, the updated base.html, and the _item_card.html partial, then update item_list.html to use {% include %} instead of its own inline item markup.
Deliberately remove {% load static %} from the top of base.html, reload the page, and record the exact error Django produces — then explain why the error points at the {% static %} line rather than at the missing {% load %} tag itself.
Explain, in your own words, why STATICFILES_DIRS being sufficient in development doesn't mean it's sufficient in production, and what collectstatic and STATIC_ROOT actually do differently.
Chapter 8 Quick Reference
- Static files —
STATICFILES_DIRSplus{% load static %}and{% static %}wire up CSS in development - _item_card.html — a reusable partial included via
{% include %}, keeping item-card markup defined in exactly one place - Real gotcha — forgetting
{% load static %}produces an error pointing at the wrong line - Real production distinction —
STATICFILES_DIRSserves files in development only;collectstaticandSTATIC_ROOThandle real production serving, covered directly in Chapter 9 - Next chapter: Deployment — taking this project from a local dev server to something actually live