Introduction
In this part, we'll dive into creating beautiful and functional templates for our project using Django's template system and Tailwind CSS. We'll also focus on writing clear documentation and comments to make our code more maintainable.
Template Organization
Our templates are organized in a hierarchical structure:
templates/
├── layout/ # Base layout templates
│ ├── base.html # Main base template
│ └── dashboard/ # Dashboard-specific layouts
│ ├── base.html # Dashboard base template
│ ├── _header.html # Header component
│ ├── _sidebar.html # Sidebar component
│ └── _footer.html # Footer component
├── projects/ # Project-related templates
│ ├── list.html # Project list view
│ ├── detail.html # Project detail view
│ ├── form.html # Project create/edit form
│ └── _project_card.html # Reusable project card component
└── components/ # Reusable UI components
├── alerts.html # Alert messages
├── forms/ # Form-related components
└── modals/ # Modal dialogs
Base Template
Here's our base template with detailed comments:
{% load static %}
</span>
lang="en">
{# Meta tags for proper rendering and SEO #}
charset="UTF-8">
name="viewport" content="width=device-width, initial-scale=1.0">
name="description" content="{% block meta_description %}Project Budget Manager{% endblock %}">
{# Dynamic title block #}
{% block title %}Project Budget Manager{% endblock %}
{# Favicon #}
rel="icon" type="image/png" href="{% static 'img/favicon.png' %}">
{# Tailwind CSS #}
href="{% static 'css/output.css' %}" rel="stylesheet">
{# Custom CSS #}
{% block extra_css %}{% endblock %}
{# Alpine.js for interactivity #}
<span class="na">defer src="https://unpkg.com/[email protected]/dist/cdn.min.js">
class="bg-gray-50 dark:bg-gray-900">
{# Main content wrapper #}
class="min-h-screen flex flex-col">
{# Content block for page-specific content #}
{% block content %}{% endblock %}
{# Footer block #}
{% block footer %}
{% include "layout/_footer.html" %}
{% endblock %}
{# Flash messages #}
{% if messages %}
class="fixed bottom-4 right-4 z-50">
{% for message in messages %}
class="alert alert-{{ message.tags }} mb-2">
{{ message }}
{% endfor %}
{% endif %}
{# JavaScript block for page-specific scripts #}
{% block extra_js %}{% endblock %}
Enter fullscreen mode
Exit fullscreen mode
Dashboard Layout
The dashboard layout extends the base template and adds navigation:
{% extends "layout/base.html" %}
{% block content %}
class="flex h-screen bg-gray-100 dark:bg-gray-900">
{# Sidebar navigation #}
{% include "layout/dashboard/_sidebar.html" %}
{# Main content area #}
class="flex-1 flex flex-col overflow-hidden">
{# Top navigation bar #}
{% include "layout/dashboard/_header.html" %}
{# Main content #}
class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-100 dark:bg-gray-900">
class="container mx-auto px-6 py-8">
{# Page header #}
class="mb-8">
class="text-2xl font-semibold text-gray-900 dark:text-white">
{% block page_title %}Dashboard{% endblock %}
{# Page-specific content #}
{% block dashboard_content %}{% endblock %}
{% endblock %}
Enter fullscreen mode
Exit fullscreen mode
Form Templates
Here's an example of a form template with proper styling and validation:
{% extends "layout/dashboard/base.html" %}
{% block dashboard_content %}
class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
class="text-lg font-medium text-gray-900 dark:text-white mb-6">
{% if form.instance.pk %}
Edit Project
{% else %}
Create New Project
{% endif %}
method="post" enctype="multipart/form-data" class="space-y-6">
{% csrf_token %}
{# Form errors #}
{% if form.non_field_errors %}
class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
{{ form.non_field_errors }}
{% endif %}
{# Project name field #}
for="{{ form.name.id_for_label }}"
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Project Name
class="mt-1">
{{ form.name }}
{% if form.name.errors %}
class="mt-2 text-sm text-red-600">
{{ form.name.errors.0 }}
{% endif %}
{# Project description field #}
for="{{ form.description.id_for_label }}"
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Description
class="mt-1">
{{ form.description }}
{% if form.description.errors %}
class="mt-2 text-sm text-red-600">
{{ form.description.errors.0 }}
{% endif %}
{# Form actions #}
class="flex justify-end space-x-4">
href="{% url 'project_list' %}"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50">
Cancel
type="submit"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700">
{% if form.instance.pk %}Save Changes{% else %}Create Project{% endif %}
{% endblock %}
Enter fullscreen mode
Exit fullscreen mode
Component Templates
Create reusable components to maintain consistency:
{% comment %}
Alert Component
Usage: {% include "components/alerts.html" with type="success" message="Operation successful" %}
Types: success, error, warning, info
{% endcomment %}
class="rounded-md p-4 mb-4
{% if type == 'success' %}
bg-green-50 text-green-800 border border-green-200
{% elif type == 'error' %}
bg-red-50 text-red-800 border border-red-200
{% elif type == 'warning' %}
bg-yellow-50 text-yellow-800 border border-yellow-200
{% else %}
bg-blue-50 text-blue-800 border border-blue-200
{% endif %}">
class="flex">
class="flex-shrink-0">
{% if type == 'success' %}
{# Success icon #}
class="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
{% elif type == 'error' %}
{# Error icon #}
class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
{% endif %}
class="ml-3">
class="text-sm">{{ message }}
Enter fullscreen mode
Exit fullscreen mode
Documentation Best Practices
File Headers: Add descriptive headers to all files:
"""
project_budget/app/views.py
This module contains the views for the Project Budget Manager application.
It handles all HTTP requests and returns appropriate responses.
Author: Your Name
Created: March 20, 2025
"""
Enter fullscreen mode
Exit fullscreen mode
Function/Class Documentation:
def calculate_project_budget(project):
"""
Calculate the total budget and expenses for a project.
Args:
project (Project): The project instance to calculate budget for
Returns:
tuple: A tuple containing (total_budget, total_expenses, remaining_budget)
Example:
>>> project = Project.objects.get(id=1)
>>> total, expenses, remaining = calculate_project_budget(project)
"""
total_budget = project.budget_set.aggregate(Sum('amount'))['amount__sum'] or 0
total_expenses = project.expense_set.aggregate(Sum('amount'))['amount__sum'] or 0
remaining_budget = total_budget - total_expenses
return total_budget, total_expenses, remaining_budget
Enter fullscreen mode
Exit fullscreen mode
Code Comments:
class ProjectListView(ListView):
model = Project
template_name = 'projects/list.html'
context_object_name = 'projects'
def get_queryset(self):
# Get the base queryset
queryset = super().get_queryset()
# Filter by user's projects only
queryset = queryset.filter(owner=self.request.user)
# Apply search filter if provided
search_query = self.request.GET.get('q')
if search_query:
queryset = queryset.filter(
Q(name__icontains=search_query) |
Q(description__icontains=search_query)
)
# Sort by status and due date
return queryset.order_by('status', 'due_date')
Enter fullscreen mode
Exit fullscreen mode
Template Tags and Filters
Create custom template tags for reusable functionality:
# app/templatetags/project_tags.py
from django import template
from django.template.defaultfilters import floatformat
register = template.Library()
@register.filter
def currency(value):
"""
Format a number as currency.
Usage:
{{ project.budget|currency }}
Example:
Input: 1234.5
Output: $1,234.50
"""
if value is None:
return '$0.00'
return f'${floatformat(value, 2)}'
@register.simple_tag
def get_project_status_class(status):
"""
Return the appropriate CSS class for a project status.
Usage:
{% get_project_status_class project.status as status_class %}
"{{ status_class }}">{{ project.status }}
"""
status_classes = {
'planning': 'bg-blue-100 text-blue-800',
'in_progress': 'bg-yellow-100 text-yellow-800',
'completed': 'bg-green-100 text-green-800',
'on_hold': 'bg-gray-100 text-gray-800',
}
return status_classes.get(status, 'bg-gray-100 text-gray-800')
Enter fullscreen mode
Exit fullscreen mode
Testing Templates
Create tests for your templates:
# app/tests/test_templates.py
from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth import get_user_model
class ProjectTemplateTests(TestCase):
def setUp(self):
self.client = Client()
self.user = get_user_model().objects.create_user(
username='testuser',
password='testpass123'
)
self.client.login(username='testuser', password='testpass123')
def test_project_list_template(self):
"""Test that the project list template renders correctly"""
response = self.client.get(reverse('project_list'))
# Check response
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'projects/list.html')
# Check context
self.assertIn('projects', response.context)
# Check page content
self.assertContains(response, 'Projects')
self.assertContains(response, 'Create New Project')
Enter fullscreen mode
Exit fullscreen mode
Next Steps
In Part 6, we'll cover advanced features like custom user management, admin dashboard customization, and utility functions. We'll also look at JavaScript integration and Tailwind configuration.
Additional Resources
Django Template Language Documentation
Tailwind CSS Documentation
Writing Great Documentation
This article is part of the "Building a Project Budget Manager with Django" series. Check out Part 1, Part 2,Part 3, and Part 4 if you haven't already!