Chapter 1: Django Prepare Template
Django Prepare Template: How to properly prepare and organize templates — so that your project stays clean, scalable, maintainable, and doesn’t turn into a nightmare when you add the 5th app or try to deploy.
Many people (especially in the first 2–3 months) just throw everything into one big templates/ folder or even worse — write HTML directly in views with HttpResponse. We are going to fix that mindset today.
I’ll explain like I’m sitting next to you in the café, showing you my screen, and we’ll build everything step-by-step in your existing polls project.
Goal for Today
We want to end up with:
- A clean, reusable base template (navbar, footer, CSS links…)
- Namespaced app templates (no name clashes when you add more apps)
- Template inheritance ({% extends %} + {% block %})
- Static files integration basics
- Context processors & global variables
- Common folder structure that almost every serious Django project in 2026 uses
Step 1: Understand Django’s Template Lookup Rules (Very Important)
Django looks for templates in these places (in this order):
- Folders listed in settings.py → TEMPLATES[‘DIRS’] (project-level templates)
- Each app’s templates/ subfolder — ifAPP_DIRS: True (default in new projects)
- Inside installed packages (third-party apps)
Golden rule for 2026:
- Never put templates directly in project root templates/ unless they are truly global (like 404.html, 500.html, base.html)
- Always put app-specific templates inside app/templates/appname/
Example correct structure:
|
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 |
mysite/ ← project root ├── manage.py ├── mysite/ │ ├── settings.py │ ├── urls.py │ └── ... ├── polls/ │ ├── templates/ │ │ └── polls/ ← namespace! │ │ ├── base.html ← can be here or in project templates │ │ ├── index.html │ │ ├── detail.html │ │ └── results.html │ ├── models.py │ └── ... └── templates/ ← optional project-level (global stuff) ├── 404.html ├── 500.html └── global_components/ ← rarely used |
Step 2: Create the Namespaced Template Folder (If Not Already Done)
Inside polls/:
|
0 1 2 3 4 5 6 |
mkdir -p polls/templates/polls |
Now every template file goes into polls/templates/polls/
Why the extra polls/ folder?
→ Prevents name collision. Imagine you later add a blog app that also has index.html. Django would get confused without the namespace.
When you write:
|
0 1 2 3 4 5 6 |
render(request, 'polls/index.html', context) |
Django knows: “look inside polls app → templates → polls → index.html”
Step 3: Create a Beautiful base.html (The Foundation)
Put this in polls/templates/polls/base.html (or in project templates/base.html if you prefer global)
|
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 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title> {% block title %} Polls App – Hyderabad 2026 {% endblock %} </title> <!-- Basic modern CSS (you can later use Tailwind CDN or compile) --> <style> :root { --primary: #006400; /* dark green – Hyderabad vibe */ --light: #f8f9fa; --dark: #333; } body { font-family: system-ui, -apple-system, sans-serif; margin: 0; background: var(--light); color: var(--dark); line-height: 1.6; } header { background: var(--primary); color: white; padding: 1.5rem; text-align: center; } nav a { color: white; margin: 0 1.2rem; text-decoration: none; font-weight: 500; } nav a:hover { text-decoration: underline; } main { max-width: 960px; margin: 2rem auto; padding: 0 1.5rem; } footer { background: #222; color: #aaa; text-align: center; padding: 1.5rem; margin-top: 4rem; } .btn { display: inline-block; padding: 0.7rem 1.4rem; background: var(--primary); color: white; border-radius: 6px; text-decoration: none; } .btn:hover { background: #004d00; } </style> </head> <body> <header> <h1>Polls – Ask Anything, Get Honest Answers</h1> <nav> <a href="{% url 'polls:index' %}">Home</a> <!-- Add more later: About, Login, etc. --> </nav> </header> <main> {% block content %} <!-- All child templates fill this block --> {% endblock %} </main> <footer> <p> © {{ current_year|default:"2026" }} — Built with ❤️ in Hyderabad using Django 6.0 </p> </footer> </body> </html> |
Step 4: Use It in Child Templates (Inheritance)
Now update polls/templates/polls/index.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 |
{% extends "polls/base.html" %} {% block title %}Latest Polls{% endblock %} {% block content %} <h2>Active Polls – as of {{ current_time|date:"d F Y" }}</h2> {% if latest_questions %} <div class="grid" style="display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));"> {% for question in latest_questions %} <div class="card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);"> <h3> <a href="{% url 'polls:detail' question.slug %}"> {{ question.question_text }} </a> </h3> <p style="color: #666; font-size: 0.95rem;"> Category: {{ question.category|title }} • Published {{ question.pub_date|timesince }} ago </p> <a href="{% url 'polls:detail' question.slug %}" class="btn">View & Vote</a> </div> {% endfor %} </div> {% else %} <div style="text-align: center; padding: 3rem; color: #666;"> <h3>No polls yet…</h3> <p>Be the first! <a href="{% url 'admin:polls_question_add' %}">Create one in admin</a></p> </div> {% endif %} {% endblock %} |
Do the same for detail.html, results.html — just change {% block content %}.
Step 5: Pass Global Variables via Context Processor (Optional but Nice)
In mysite/settings.py → TEMPLATES → OPTIONS → ‘context_processors’:
Make sure these are there (usually default):
|
0 1 2 3 4 5 6 7 8 9 10 11 |
'context_processors': [ ... 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ] |
Add a custom one for current year:
Create mysite/context_processors.py:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
from datetime import datetime def global_vars(request): return { 'current_year': datetime.now().year, 'site_name': 'Hyderabad Polls', } |
Then in settings.py → add to context_processors:
|
0 1 2 3 4 5 6 7 8 9 |
'context_processors': [ ... 'mysite.context_processors.global_vars', ] |
Now {{ current_year }} and {{ site_name }} work in every template without passing them manually.
Step 6: Quick Checklist – Did You Prepare Templates Correctly?
- All app templates are in app/templates/appname/
- You use {% extends “polls/base.html” %} in every page
- You use {% block title %}, {% block content %}, maybe {% block extra_css %}
- You use {% url ‘polls:detail’ question.slug %} — never hardcode URLs
- You use filters: |date, |timesince, |pluralize, |default, |title
- You have basic CSS in base.html (or link to static later)
Common Beginner Mistakes (and Fixes)
- TemplateDoesNotExist: index.html → Forgot the polls/ namespace → should be ‘polls/index.html’
- Block not showing → Forgot {% block content %} in child or mismatched name
- CSS not loading → Later we’ll move to {% static %} — don’t put <link> yet
- NoReverseMatch → Forgot app_name = ‘polls’ in polls/urls.py
Your Mini Homework
- Create base.html as shown
- Update index.html and detail.html to extend it
- Add 2–3 questions in admin → visit /polls/
- Try adding {% block extra_head %} in child template for page-specific CSS
Tell me next:
- “Done! Now show me how to add static files (CSS, JS, images) properly”
- “How to make navbar highlight current page?”
- “I want to use Tailwind or Bootstrap – how?”
- “Got error: TemplateSyntaxError – help!”
- Or next topic: Forms & Voting (POST requests)
You’re doing amazing — templates are now clean and professional! Let’s keep building this thing into something you can show your friends 😄🚀
