Chapter 3: Django Add Master Template
Django Add Master Template: How to create and use a proper master / base template (also called layout template, parent template, skeleton template…)
This is the file that appears on every single page of your site — header, navigation bar, footer, global CSS/JS links, meta tags, favicon, etc.
Right now many beginners either:
- copy-paste the same <html><head><body> into every single template (DRY violation — nightmare to maintain)
- or they skip it completely and end up with inconsistent design
We are going to fix that today — step by step, very carefully, so that your polls app (and any future apps) look professional and are easy to maintain for years.
Goal for Today
We want one single master template (base.html) that:
- Contains all repeating parts (header, nav, footer, <head> tags…)
- Lets every page extend it and only fill in the changing content
- Supports page-specific title, extra CSS/JS, meta tags, body classes, etc.
- Works with both function-based and class-based views
- Is ready for static files (CSS, JS, images) — we’ll touch this lightly today
Step 1: Where Should base.html Live? (Two Popular Choices)
| Location | When to choose this | Pros | Cons |
|---|---|---|---|
| polls/templates/polls/base.html | You have only one app right now | Simple, everything in one app | When you add second app → duplication |
| mysite/templates/base.html | You plan to have multiple apps (recommended) | Truly global — used by all apps | Slightly more setup in settings |
Recommendation for you right now (2026 best practice): Put it in the project-level templates/ folder → this is what almost every medium+ Django project does
Step 2: Create Project-Level Templates Folder
In your project root (next to manage.py):
|
0 1 2 3 4 5 6 |
mkdir templates |
Now your folder looks like:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
mysite-project/ ├── manage.py ├── mysite/ │ ├── settings.py │ ├── urls.py │ └── ... ├── polls/ │ ├── templates/ │ │ └── polls/ │ │ ├── index.html │ │ └── detail.html ├── templates/ ← NEW – global templates │ └── base.html ← will create now └── venv/ |
Step 3: Tell Django to Look in Project-Level Templates
Open mysite/settings.py
Find the TEMPLATES setting (it’s a list with one big dict)
Make sure ‘DIRS’ includes your new folder:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], # ← add this line! 'APP_DIRS': True, ... }, ] |
→ Now Django will look first in mysite/templates/, then in each app’s templates/
Step 4: Create the Master Template – base.html
Create mysite/templates/base.html
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Page-specific title – child templates override this --> <title> {% block title %} Polls App – Ask & Vote – Hyderabad 2026 {% endblock %} {% block title_suffix %} | Polls{% endblock %} </title> <!-- Meta tags – can be overridden per page --> {% block meta %} <meta name="description" content="Create and vote on fun polls built with Django"> {% endblock %} <!-- Global CSS – later move to static files --> <style> :root { --primary: #006400; /* dark green – Telangana/Hyderabad feel */ --primary-dark: #004d00; --light-bg: #f8f9fa; --text: #2d2d2d; --gray: #6c757d; } * { box-sizing: border-box; margin:0; padding:0; } body { font-family: system-ui, sans-serif; background: var(--light-bg); color: var(--text); line-height: 1.6; } header { background: var(--primary); color: white; padding: 1.8rem 1rem; text-align: center; box-shadow: 0 2px 10px rgba(0,0,0,0.15); } header h1 { margin: 0; font-size: 2.2rem; } nav { margin-top: 1rem; } nav a { color: white; margin: 0 1.2rem; font-weight: 500; text-decoration: none; transition: all 0.2s; } nav a:hover { text-decoration: underline; opacity: 0.9; } main { max-width: 1100px; margin: 2.5rem auto; padding: 0 1.5rem; } footer { background: #222; color: #aaa; text-align: center; padding: 2rem 1rem; margin-top: 5rem; font-size: 0.95rem; } .container { background: white; padding: 2rem; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); } .btn { display: inline-block; padding: 0.8rem 1.6rem; background: var(--primary); color: white; border: none; border-radius: 6px; text-decoration: none; font-weight: 500; cursor: pointer; transition: background 0.2s; } .btn:hover { background: var(--primary-dark); } </style> <!-- Child templates can add extra CSS/JS here --> {% block extra_head %}{% endblock %} </head> <body> <header> <h1>Polls – Real Opinions, No Drama</h1> <nav> <a href="{% url 'polls:index' %}">Home</a> <!-- Later: <a href="{% url 'polls:about' %}">About</a> --> <!-- <a href="{% url 'admin:index' %}">Admin</a> --> </nav> </header> <main> {% block content %} <!-- This is where 95% of your page content goes --> <p style="color:red; text-align:center;"> [Placeholder – override {% block content %} in child template] </p> {% endblock %} </main> <footer> <p> © {{ current_year|default:"2026" }} — Built with Django 6.0 • Made with ❤️ in <strong>Hyderabad</strong> </p> </footer> <!-- Global JS or analytics can go here --> {% block extra_js %}{% endblock %} </body> </html> |
Step 5: Convert Existing Templates to Extend base.html
Open polls/templates/polls/index.html
Delete everything except the changing part and wrap it:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
{% extends "base.html" %} {% block title %}Latest Polls – {{ block.super }}{% endblock %} {% block extra_head %} <!-- Page-specific CSS if needed --> <style> .question-grid { display: grid; gap: 1.8rem; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); } </style> {% endblock %} {% block content %} <div class="container"> <h2>Active Polls Right Now</h2> {% if latest_questions %} <div class="question-grid"> {% for question in latest_questions %} <div style="background:#fff; padding:1.6rem; border-radius:10px; box-shadow:0 3px 12px rgba(0,0,0,0.07);"> <h3 style="margin-top:0;"> <a href="{% url 'polls:detail' slug=question.slug %}" style="color:var(--primary); text-decoration:none;"> {{ question.question_text }} </a> </h3> <div style="color:var(--gray); font-size:0.95rem; margin:0.8rem 0 1.2rem;"> {{ question.category|title }} • {{ question.pub_date|timesince }} ago </div> <a href="{% url 'polls:detail' slug=question.slug %}" class="btn">View & Vote →</a> </div> {% endfor %} </div> {% else %} <div style="text-align:center; padding:4rem 1rem; color:var(--gray);"> <h3>No polls available yet…</h3> <p>Why not <a href="{% url 'admin:polls_question_add' %}">create the first one</a>?</p> </div> {% endif %} </div> {% endblock %} |
Do exactly the same for detail.html, results.html, etc.
Step 6: Quick Test
- Make sure you added ‘DIRS’: [BASE_DIR / ‘templates’] in settings
- Run python manage.py runserver
- Visit http://127.0.0.1:8000/polls/
- Check:
- Header & footer appear
- Title is correct
- Clicking question → detail page also has same header/footer
- Design is consistent
Common Gotchas & Fixes
| Problem | Likely Cause | Fix |
|---|---|---|
| base.html not found | Forgot to add DIRS in settings | Add BASE_DIR / ‘templates’ |
| Header/footer still missing | Forgot {% extends “base.html” %} | Add at very top of every child template |
| Title shows only default | Forgot {% block title %} in child | Add {% block title %}My Page{% endblock %} |
| CSS not applying | Inline style conflict or missing block | Put page-specific styles in {% block extra_head %} |
| current_year not available | No context processor | Add custom context processor (see earlier lesson) |
Your Mini Task Right Now
- Create templates/base.html exactly as shown
- Update index.html and detail.html to extend it
- Add at least one extra block (e.g. {% block extra_head %} with a page-specific style)
- Visit both pages — enjoy the consistent look!
Tell me what you want next:
- “Done! Now show me how to move CSS/JS to static files properly”
- “How to make navbar show active link (current page highlight)?”
- “I want to add Bootstrap 5 or Tailwind via CDN – example?”
- “Got error TemplateDoesNotExist: base.html – help!”
- Or finally: “Let’s add the voting form + POST handling + F() update”
You’ve just built the foundation that makes every future page 10× faster to create. Super proud of how far you’ve come — let’s keep this momentum! 🚀🇮🇳
