A comprehensive step-by-step guide to building a full-stack CRUD (Create, Read, Update, Delete) application using Django framework. This tutorial covers all Django basics from scratch, perfect for beginners.
β οΈ Windows PowerShell Users: Before running step 3, you may need to allow script execution:Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserSee Step 3 for detailed instructions.
# 1. Clone the repository
git clone https://github.com/NoelPOS/django-task-manager.git
cd django-task-manager
# 2. Create virtual environment
python -m venv myenv
# 3. Activate virtual environment
.\myenv\Scripts\Activate.ps1
# If you get an error, run in admin PowerShell: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Or use Command Prompt: myenv\Scripts\activate.bat
# 4. Install dependencies
pip install -r requirements.txt
# 5. Run migrations
python manage.py migrate
# 6. Create superuser (optional)
python manage.py createsuperuser
# 7. Run server
python manage.py runserver
# 8. Open http://127.0.0.1:8000/ in your browser- Prerequisites & Setup
- Understanding the Project Structure
- Step-by-Step Build Instructions
- Django Concepts Explained
- Testing the Application
- Troubleshooting
- Download from https://code.visualstudio.com/
- Install on your system
- Download Python 3.8+ from https://www.python.org/downloads/
- During installation, check "Add Python to PATH"
- Verify installation: Open terminal and run
python --version
Open VS Code and install these extensions:
- Python (by Microsoft) - Python language support
- Pylance (by Microsoft) - Fast Python language server
- Django (by Baptiste Darthenay) - Django template syntax highlighting
- SQLite Viewer (by Florian Klampfer) - View SQLite databases
To install extensions:
- Click Extensions icon in VS Code sidebar (or press
Ctrl+Shift+X) - Search for extension name
- Click "Install"
After completing this tutorial, your project will look like this:
Django-starter/
β
βββ myenv/ # Virtual environment (isolated Python packages)
β
βββ myproject/ # Django project (main configuration)
β βββ __init__.py # Makes this a Python package
β βββ settings.py # Project settings (database, apps, etc.)
β βββ urls.py # Main URL routing configuration
β βββ wsgi.py # Web server gateway interface
β βββ asgi.py # Asynchronous server gateway interface
β
βββ tasks/ # Django app (specific functionality)
β βββ migrations/ # Database migration files
β β βββ 0001_initial.py # First migration (creates Task table)
β βββ templates/ # HTML templates for this app
β β βββ tasks/
β β βββ base.html # Base template (inherited by others)
β β βββ task_list.html # Display all tasks
β β βββ task_form.html # Create/Update form
β β βββ task_detail.html # Single task view
β β βββ task_confirm_delete.html # Delete confirmation
β βββ __init__.py # Makes this a Python package
β βββ admin.py # Admin panel configuration
β βββ apps.py # App configuration
β βββ models.py # Database models (Task model)
β βββ forms.py # Form definitions (TaskForm)
β βββ views.py # View functions (business logic)
β βββ urls.py # App-specific URL patterns
β βββ tests.py # Unit tests (optional)
β
βββ db.sqlite3 # SQLite database file
βββ manage.py # Django command-line utility
βββ README.md # This file
# Create a new folder for your project
mkdir Django-starter
cd Django-starterWhy? Organizing your project in a dedicated folder keeps everything clean and manageable.
# Create virtual environment named 'myenv'
python -m venv myenvWhy Virtual Environment?
- Isolates project dependencies from global Python installation
- Different projects can use different package versions
- Makes project portable and reproducible
- Prevents version conflicts
Before activating the virtual environment, you may need to allow PowerShell script execution. If you see an error like:
.\myenv\Scripts\Activate.ps1 : File cannot be loaded because running scripts is disabled on this system.
Fix it with these steps:
-
Open PowerShell as Administrator:
- Press
Windows Key - Type "PowerShell"
- Right-click "Windows PowerShell"
- Select "Run as administrator"
- Press
-
Check current execution policy:
Get-ExecutionPolicyIf it shows
Restricted, you need to change it. -
Set execution policy (choose ONE option):
Option A: Recommended (Current User Only)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
- Type
Yand press Enter to confirm - This only affects your user account, not the entire system
- Allows local scripts to run
Option B: For All Users (Requires Admin)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine
- Type
Yand press Enter to confirm - Affects all users on the computer
- May be blocked by company policies
- Type
-
Verify the change:
Get-ExecutionPolicyShould now show
RemoteSigned -
Close the Admin PowerShell and return to your regular terminal in VS Code
Now Activate the Virtual Environment:
# Windows PowerShell (after setting execution policy)
.\myenv\Scripts\Activate.ps1
# Windows Command Prompt (cmd) - no execution policy needed
myenv\Scripts\activate.bat
# Git Bash (alternative)
source myenv/Scripts/activateYou should see (myenv) prefix in your terminal, indicating the environment is active.
Example:
Before: PS C:\Users\Saw\Desktop\Django-starter>
After: (myenv) PS C:\Users\Saw\Desktop\Django-starter>
Why Activate? All packages you install will only be available in this environment, not globally.
Troubleshooting:
| Issue | Solution |
|---|---|
| "cannot be loaded" error | Follow PowerShell execution policy steps above |
| Still getting errors after policy change | Close and reopen terminal/VS Code |
| "Activate.ps1 not found" | Make sure you're in the Django-starter directory |
(myenv) doesn't appear |
Try the cmd version: myenv\Scripts\activate.bat |
| Company/school computer blocks policy change | Use Command Prompt (cmd) instead of PowerShell |
Alternative if PowerShell doesn't work: Use Command Prompt (cmd) which doesn't have execution policy restrictions:
- Open new terminal in VS Code: Click
+dropdown β Select "Command Prompt" - Run:
myenv\Scripts\activate.bat
pip install djangoWhat This Does:
- Downloads Django framework and its dependencies
- Installs them in your virtual environment
- Makes Django commands available
Verify Installation:
python -m django --versiondjango-admin startproject myproject .Why the dot (.) at the end?
- Creates project in current directory
- Without dot: creates extra nested folder
- With dot: cleaner structure
What This Creates:
myproject/- Main configuration foldermanage.py- Command-line utility for Django tasks
Important Files Created:
settings.py- All project settings (database, apps, middleware)urls.py- URL routing (maps URLs to views)wsgi.py&asgi.py- Deployment configuration
python manage.py startapp tasksProject vs App - What's the Difference?
- Project: Entire website with all configuration
- App: Specific functionality (e.g., blog, shop, tasks)
- One project can have multiple apps
- Apps can be reused in different projects
What This Creates:
tasks/folder with basic app structuremodels.py- Define database tablesviews.py- Handle requests and responsesadmin.py- Admin panel configurationtests.py- Write tests for your app
Open myproject/settings.py and add 'tasks' to INSTALLED_APPS:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'tasks', # Our custom app
]Why Register?
- Django needs to know about your app
- Without registration, models won't work
- Templates won't be found
- URLs won't be recognized
Open tasks/models.py and define the Task model:
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(blank=True, null=True)
completed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.titleField Types Explained:
CharField- Short text (requires max_length)TextField- Long text (no length limit)BooleanField- True/False valuesDateTimeField- Date and timeauto_now_add=True- Set once when createdauto_now=True- Update every time object is saved
Meta Class:
ordering- Default sort order (- means descending)
str Method:
- Defines how object appears in admin and shell
- Returns human-readable representation
# Create migration files
python manage.py makemigrations
# Apply migrations to database
python manage.py migrateWhat Are Migrations?
- Version control for your database schema
- Tracks changes to models
- Can be applied/reversed
- Makes database changes safe and reversible
makemigrations:
- Reads your models
- Creates Python files describing changes
- Stored in
migrations/folder
migrate:
- Executes migration files
- Creates/modifies database tables
- Applies changes in correct order
What You've Accomplished So Far:
- β Created Django project and app
- β Registered app in settings
- β Defined Task model
- β Created and applied migrations
- β Database is now ready!
Test It Now:
-
Check that database file was created: Look in your project folder - you should see a file named
db.sqlite3- File size: Around 130+ KB
- This is your SQLite database containing the Task table
-
Verify migrations were applied:
python manage.py showmigrations
Expected output:
tasks [X] 0001_initialThe
[X]means the migration was successfully applied! -
Test database access using Django shell:
python manage.py shell
Then type these commands in the shell:
from tasks.models import Task # Create a test task task = Task.objects.create( title="Test Task", description="Testing database connection" ) # Verify it was saved print(f"Created task #{task.id}: {task.title}") # Count tasks in database count = Task.objects.count() print(f"Total tasks: {count}") # Exit shell exit()
Expected output:
Created task #1: Test Task Total tasks: 1
What You CAN'T Do Yet:
- β View tasks in a browser (no URLs or templates yet)
- β Create tasks via web interface (no forms hooked up yet)
- β See task list on a webpage (templates coming in Step 13)
What's Next: In Steps 10-13, you'll build the web interface (forms, views, URLs, templates) to interact with this database through your browser!
Create tasks/forms.py:
from django import forms
from .models import Task
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ['title', 'description', 'completed']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control'}),
'completed': forms.CheckboxInput(attrs={'class': 'form-check-input'})
}What Are Forms?
- Handle user input
- Validate data automatically
- Generate HTML form fields
- Clean and sanitize data
ModelForm:
- Automatically creates form from model
- Fields match model fields
- Validation rules from model
- Saves directly to database
Widgets:
- Control HTML rendering
- Add CSS classes
- Customize appearance
- Add HTML attributes
Edit tasks/views.py:
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from .models import Task
from .forms import TaskForm
def task_list(request):
"""Display all tasks"""
tasks = Task.objects.all()
return render(request, 'tasks/task_list.html', {'tasks': tasks})
def task_create(request):
"""Create new task"""
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, 'Task created successfully!')
return redirect('task_list')
else:
form = TaskForm()
return render(request, 'tasks/task_form.html', {'form': form})
def task_update(request, pk):
"""Update existing task"""
task = get_object_or_404(Task, pk=pk)
if request.method == 'POST':
form = TaskForm(request.POST, instance=task)
if form.is_valid():
form.save()
messages.success(request, 'Task updated!')
return redirect('task_list')
else:
form = TaskForm(instance=task)
return render(request, 'tasks/task_form.html', {'form': form})
def task_delete(request, pk):
"""Delete task"""
task = get_object_or_404(Task, pk=pk)
if request.method == 'POST':
task.delete()
messages.success(request, 'Task deleted!')
return redirect('task_list')
return render(request, 'tasks/task_confirm_delete.html', {'task': task})
def task_detail(request, pk):
"""View single task"""
task = get_object_or_404(Task, pk=pk)
return render(request, 'tasks/task_detail.html', {'task': task})View Function Components:
request- Contains all request data (GET/POST, user, etc.)render()- Combines template with data, returns HTMLredirect()- Sends user to different URLget_object_or_404()- Gets object or shows 404 page
Request Methods:
GET- Fetch data (show form, display page)POST- Submit data (create, update, delete)
What You've Built So Far:
- β Database with Task model
- β Forms for data validation
- β View functions (business logic)
- β All Python code is ready!
What's Still Missing:
- β URL routing (Django doesn't know which URLs call which views)
- β HTML templates (no web pages to display)
- β Can't test in browser yet
Quick Code Review:
At this point, your project structure should look like this:
Django-starter/
βββ db.sqlite3 β
Database created
βββ manage.py β
Django command tool
βββ myproject/
β βββ settings.py β
tasks app registered
β βββ urls.py β³ Will update in Step 12
βββ tasks/
βββ models.py β
Task model defined
βββ forms.py β
TaskForm created
βββ views.py β
5 view functions ready
βββ urls.py β Doesn't exist yet (Step 12)
βββ templates/ β Doesn't exist yet (Step 13)
Verify Your Views File:
Open tasks/views.py and make sure you have all 5 functions:
- β
task_list(request)- Show all tasks - β
task_create(request)- Create new task - β
task_update(request, pk)- Update existing task - β
task_delete(request, pk)- Delete task - β
task_detail(request, pk)- View one task
What Happens If You Start the Server Now:
python manage.py runserverThen visit http://127.0.0.1:8000/
You'll see: "Page not found (404)" error
Why? Because:
- No URLs are configured yet (Step 12)
- No templates exist yet (Step 13)
- Django doesn't know what to show!
This is NORMAL! You're building from the inside out:
- β Database layer (models) - Done
- β Business logic (views) - Done
- β³ Routing (URLs) - Next in Step 12
- β³ Presentation (templates) - After that in Step 13
What's Next: Step 12 will connect URLs to your view functions, and Step 13 will create the beautiful web pages!
URL routing is how Django maps web addresses (URLs) to view functions. We'll create two URL configuration files: one for our tasks app and one to connect everything in the main project.
What You'll Do:
Create a new file called urls.py inside the tasks folder to define all the URL patterns for task-related pages.
Steps:
- Navigate to the
tasksfolder in VS Code - Right-click on the
tasksfolder β New File β name iturls.py - Copy and paste the following code into
tasks/urls.py:
from django.urls import path
from . import views
"""
URL patterns for the tasks app.
Each path() connects a URL to a view function.
"""
urlpatterns = [
# List all tasks - Homepage
# When user visits: http://127.0.0.1:8000/
# Django calls: views.task_list function
path('', views.task_list, name='task_list'),
# Create new task
# When user visits: http://127.0.0.1:8000/create/
# Django calls: views.task_create function
path('create/', views.task_create, name='task_create'),
# View single task details
# When user visits: http://127.0.0.1:8000/task/5/
# Django captures "5" as pk (primary key) and calls: views.task_detail(request, pk=5)
path('task/<int:pk>/', views.task_detail, name='task_detail'),
# Update existing task
# When user visits: http://127.0.0.1:8000/task/5/update/
# Django captures "5" and calls: views.task_update(request, pk=5)
path('task/<int:pk>/update/', views.task_update, name='task_update'),
# Delete task
# When user visits: http://127.0.0.1:8000/task/5/delete/
# Django captures "5" and calls: views.task_delete(request, pk=5)
path('task/<int:pk>/delete/', views.task_delete, name='task_delete'),
]Understanding the Code:
from django.urls import path- Imports thepath()function used to define URL patternsfrom . import views- Imports view functions fromviews.pyin the same folder (the.means current directory)urlpatterns = [...]- List of URL patterns Django will check in orderpath('url-string', view_function, name='url-name')- Basic syntax:- First argument (
'url-string') - The URL pattern to match - Second argument (
view_function) - The function to call when URL matches - Third argument (
name='url-name') - A unique name to reference this URL in templates
- First argument (
<int:pk>- URL parameter that:- Captures an integer from the URL
- Stores it in a variable named
pk(primary key) - Passes it to the view function as a parameter
- Example:
/task/5/βpk=5,/task/123/βpk=123
Why We Use Named URLs:
Instead of hardcoding URLs like /task/5/update/, we can use {% url 'task_update' 5 %} in templates. This makes code maintainableβif you change the URL pattern later, all links automatically update!
Save the file (Ctrl+S or Cmd+S)
What You'll Do:
Modify the main project's URL configuration to include all the URLs from your tasks app.
Steps:
- Open
myproject/urls.pyin VS Code - Replace the entire file content with the following code:
"""
URL configuration for myproject project.
This is the main URL router for the entire Django project.
It routes requests to the appropriate app-level URL configurations.
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
# Django admin interface
# Access at: http://127.0.0.1:8000/admin/
# This provides a built-in interface to manage your database
path('admin/', admin.site.urls),
# Include all URLs from tasks app
# The empty string '' means tasks URLs will be at the root level
# Example: http://127.0.0.1:8000/ (not http://127.0.0.1:8000/tasks/)
#
# Alternative: path('tasks/', include('tasks.urls'))
# Would make URLs like: http://127.0.0.1:8000/tasks/create/
path('', include('tasks.urls')),
]Understanding the Code:
from django.urls import path, include- Imports bothpath()andinclude()functionsinclude('tasks.urls')- Includes all URL patterns fromtasks/urls.py- This keeps code modular and organized
- Each app manages its own URLs
- Makes apps reusable in other projects
path('admin/', admin.site.urls)- Built-in Django admin interface- Empty string in
path('', include(...))- Makes task URLs available at root level- Task list:
http://127.0.0.1:8000/(not/tasks/) - Create task:
http://127.0.0.1:8000/create/(not/tasks/create/)
- Task list:
Why Two URL Files?
myproject/urls.py (Main Router)
βββ /admin/ β Django admin
βββ / β include('tasks.urls')
βββ / β task_list
βββ /create/ β task_create
βββ /task/5/ β task_detail
βββ /task/5/update/ β task_update
βββ /task/5/delete/ β task_delete
This structure:
- Keeps projects organized - Each app manages its own routes
- Makes apps reusable - You can use the tasks app in another project
- Avoids conflicts - Different apps can have same URL names (namespaced)
Save the file (Ctrl+S or Cmd+S)
What to Expect:
At this point, your URLs are configured but won't work yet because we haven't created the template files. However, we can verify that Django recognizes the URL patterns.
Test It:
-
Make sure your development server is running:
python manage.py runserver
-
You should see output like:
Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). November 28, 2025 - 00:04:26 Django version 5.2, using settings 'myproject.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CTRL-BREAK. -
Open your browser and try visiting
http://127.0.0.1:8000/ -
Expected Result:
You'll see an error like:TemplateDoesNotExist at / tasks/task_list.htmlThis is GOOD! It means:
- β
Django found the URL pattern (matched
''totask_list) - β
Django called the
task_listview function - β
The view function tried to render
tasks/task_list.html - β The template doesn't exist yet (we'll create it in Step 13)
- β
Django found the URL pattern (matched
-
Try other URLs to verify URL routing works:
http://127.0.0.1:8000/create/β Should show error fortask_form.htmlhttp://127.0.0.1:8000/task/1/β Should show error fortask_detail.htmlhttp://127.0.0.1:8000/admin/β Should show Django admin login page (this works!)
Common Errors:
| Error | Meaning | Solution |
|---|---|---|
Page not found (404) |
Django can't find matching URL | Check that you saved both URL files |
ModuleNotFoundError: No module named 'tasks.urls' |
Django can't import tasks.urls | Make sure tasks/urls.py exists |
ImportError: cannot import name 'views' |
Can't import views | Verify tasks/views.py exists from Step 11 |
AttributeError: module 'tasks.views' has no attribute 'task_list' |
View function doesn't exist | Make sure you completed Step 11 (Create Views) |
What's Next?
In Step 13, we'll create the HTML templates that Django is looking for. These templates will display your tasks and forms in a beautiful, user-friendly interface.
Templates are HTML files that Django uses to generate web pages. We'll create 5 templates that work together to display your task manager application.
What You'll Do:
Create a nested folder structure where Django will look for your template files.
Steps:
- Navigate to the
tasksfolder in VS Code - Create a new folder named
templatesinsidetasks - Inside the
templatesfolder, create another folder namedtasks
Final Structure:
tasks/
βββ templates/
β βββ tasks/ β All HTML files go here
β βββ base.html
β βββ task_list.html
β βββ task_form.html
β βββ task_detail.html
β βββ task_confirm_delete.html
Why Nested tasks/templates/tasks/?
- Django searches ALL
templates/folders in ALL installed apps - Nesting prevents name conflicts between different apps
- When you reference templates in code, use
'tasks/base.html'not just'base.html' - Example: If you had a
blogapp, it could haveblog/templates/blog/base.htmlwithout conflicting
What This Does:
The base template contains common elements (navigation bar, footer, styling) that all other templates will inherit. This promotes DRY (Don't Repeat Yourself) principle.
Steps:
- Inside
tasks/templates/tasks/folder, create a new file namedbase.html - Copy and paste the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Task Manager{% endblock %}</title>
<!-- Bootstrap CSS for styling -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #f8f9fa;
}
.navbar-brand {
font-weight: bold;
}
.main-content {
margin-top: 20px;
margin-bottom: 40px;
}
</style>
</head>
<body>
<!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="{% url 'task_list' %}">π Task Manager</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'task_list' %}">All Tasks</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'task_create' %}">Create Task</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/">Admin Panel</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Messages (for success/error notifications) -->
<div class="container mt-3">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
</div>
<!-- Main Content Area - child templates will fill this -->
<div class="container main-content">
{% block content %}
{% endblock %}
</div>
<!-- Footer -->
<footer class="bg-light text-center text-muted py-3 mt-auto">
<div class="container">
<p class="mb-0">Django CRUD Task Manager Β© 2025</p>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>Understanding the Code:
{% block title %}...{% endblock %}- Defines a replaceable section that child templates can override{% url 'task_list' %}- Django template tag that generates URL based on the name we defined inurls.py- Benefit: If you change URL pattern later, links update automatically
- Much better than hardcoding
/or/create/
{% if messages %}- Displays success/error messages from views- Remember in
views.pywe usedmessages.success(request, 'Task created!') - This is where those messages appear
- Remember in
{% for message in messages %}- Loops through all messages{{ message }}- Outputs the message text{{ message.tags }}- Gets message type (success, error, warning) for styling- Bootstrap Classes:
navbar,container,alert,btn- Pre-built Bootstrap CSS classes- Makes the app look professional without writing custom CSS
{% block content %}{% endblock %}- Empty block that child templates will fill with their specific content
Save the file (Ctrl+S)
What This Does:
Displays all your tasks in a grid layout with options to view, edit, or delete each task.
Steps:
- Inside
tasks/templates/tasks/folder, createtask_list.html - Copy and paste the following code:
{% extends 'tasks/base.html' %}
{% block title %}All Tasks - Task Manager{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>All Tasks</h2>
<a href="{% url 'task_create' %}" class="btn btn-success">
+ New Task
</a>
</div>
{% if tasks %}
<div class="row">
{% for task in tasks %}
<div class="col-md-6 mb-3">
<div class="card {% if task.completed %}border-success{% endif %}">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<h5 class="card-title">
{% if task.completed %}
<span class="badge bg-success me-2">β</span>
<del>{{ task.title }}</del>
{% else %}
<span class="badge bg-warning text-dark me-2">β³</span>
{{ task.title }}
{% endif %}
</h5>
</div>
{% if task.description %}
<p class="card-text text-muted">
{{ task.description|truncatewords:20 }}
</p>
{% endif %}
<div class="text-muted small mb-3">
<span>Created: {{ task.created_at|date:"M d, Y" }}</span>
</div>
<div class="btn-group" role="group">
<a href="{% url 'task_detail' task.pk %}" class="btn btn-sm btn-info">
View
</a>
<a href="{% url 'task_update' task.pk %}" class="btn btn-sm btn-primary">
Edit
</a>
<a href="{% url 'task_delete' task.pk %}" class="btn btn-sm btn-danger">
Delete
</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="alert alert-info text-center">
<h4>No tasks yet!</h4>
<p>Create your first task to get started.</p>
<a href="{% url 'task_create' %}" class="btn btn-primary">Create Task</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}Understanding the Code:
{% extends 'tasks/base.html' %}- Inherits structure frombase.html{% if tasks %}- Checks if any tasks exist in database{% for task in tasks %}- Loops through each task from the view's context- Remember:
task_listview sends{'tasks': tasks}to template
- Remember:
{% if task.completed %}- Conditional styling based on task status{{ task.title }}- Outputs the task's title{{ task.description|truncatewords:20 }}- Template filter that limits description to 20 words{{ task.created_at|date:"M d, Y" }}- Formats date as "Nov 28, 2025"{% url 'task_detail' task.pk %}- Generates URL like/task/5/task.pkis the task's primary key (ID)- This gets passed as the
pkparameter to the view
{% else %}- Shown when no tasks exist- Bootstrap Grid:
col-md-6- Each task card takes 50% width on medium+ screens- Creates 2-column layout
- Automatically stacks on mobile devices
Save the file
What This Does:
Provides a form to create new tasks or update existing tasks. The same template handles both operations.
Steps:
- Inside
tasks/templates/tasks/folder, createtask_form.html - Copy and paste the following code:
{% extends 'tasks/base.html' %}
{% block title %}{{ action }} Task - Task Manager{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">{{ action }} Task</h3>
</div>
<div class="card-body">
<!-- Form submission -->
<!-- method="post" sends data to server -->
<!-- action="" submits to current URL -->
<form method="post" action="">
<!-- CSRF token - required for security in Django POST forms -->
{% csrf_token %}
<!-- Display form errors if any -->
{% if form.errors %}
<div class="alert alert-danger">
<strong>Please correct the errors below:</strong>
{{ form.errors }}
</div>
{% endif %}
<!-- Render form fields -->
<div class="mb-3">
<label for="{{ form.title.id_for_label }}" class="form-label">
{{ form.title.label }}
</label>
{{ form.title }}
{% if form.title.errors %}
<div class="text-danger">{{ form.title.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.description.id_for_label }}" class="form-label">
{{ form.description.label }}
</label>
{{ form.description }}
{% if form.description.errors %}
<div class="text-danger">{{ form.description.errors }}</div>
{% endif %}
</div>
<div class="mb-3 form-check">
{{ form.completed }}
<label for="{{ form.completed.id_for_label }}" class="form-check-label">
{{ form.completed.label }}
</label>
{% if form.completed.errors %}
<div class="text-danger">{{ form.completed.errors }}</div>
{% endif %}
</div>
<!-- Submit buttons -->
<div class="d-flex justify-content-between">
<button type="submit" class="btn btn-success">
{{ action }} Task
</button>
<a href="{% url 'task_list' %}" class="btn btn-secondary">
Cancel
</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}Understanding the Code:
{{ action }}- Variable passed from view (either "Create" or "Update")<form method="post" action="">- HTML form that submits via POST methodaction=""means submit to same URL
{% csrf_token %}- CRITICAL SECURITY FEATURE- Cross-Site Request Forgery protection
- Django requires this in all POST forms
- Without it, form submission will fail with 403 Forbidden error
{{ form.title }}- Renders the title input field- Django automatically generates HTML:
<input type="text" name="title" class="form-control" /> - Styling comes from
widgetswe defined informs.py
- Django automatically generates HTML:
{{ form.title.label }}- Outputs "Title" label text{{ form.title.id_for_label }}- Gets the HTML id attribute for the input{% if form.errors %}- Shows validation errors- How This Template Works for Both Create and Update:
- Create: View passes empty form +
action="Create" - Update: View passes form with existing data +
action="Update" - Same template, different context!
- Create: View passes empty form +
Save the file
What This Does:
Shows complete information about a single task, including creation and last updated timestamps.
Steps:
- Inside
tasks/templates/tasks/folder, createtask_detail.html - Copy and paste the following code:
{% extends 'tasks/base.html' %}
{% block title %}{{ task.title }} - Task Manager{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
<h3 class="mb-0">Task Details</h3>
{% if task.completed %}
<span class="badge bg-success">Completed β</span>
{% else %}
<span class="badge bg-warning text-dark">Pending β³</span>
{% endif %}
</div>
<div class="card-body">
<h4 class="card-title">{{ task.title }}</h4>
{% if task.description %}
<div class="mb-3">
<h6 class="text-muted">Description:</h6>
<p class="card-text">{{ task.description }}</p>
</div>
{% else %}
<p class="text-muted fst-italic">No description provided.</p>
{% endif %}
<hr>
<div class="row">
<div class="col-md-6">
<p class="mb-1"><strong>Created:</strong></p>
<p class="text-muted">{{ task.created_at|date:"F d, Y g:i A" }}</p>
</div>
<div class="col-md-6">
<p class="mb-1"><strong>Last Updated:</strong></p>
<p class="text-muted">{{ task.updated_at|date:"F d, Y g:i A" }}</p>
</div>
</div>
<hr>
<div class="d-flex gap-2">
<a href="{% url 'task_update' task.pk %}" class="btn btn-primary">
Edit Task
</a>
<a href="{% url 'task_delete' task.pk %}" class="btn btn-danger">
Delete Task
</a>
<a href="{% url 'task_list' %}" class="btn btn-secondary">
Back to List
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}Understanding the Code:
{{ task.title }}- Accesses task object passed fromtask_detailview- View sends:
{'task': task} - Template uses:
{{ task.title }},{{ task.description }}, etc.
- View sends:
{{ task.created_at|date:"F d, Y g:i A" }}- Formats as "November 28, 2025 12:30 PM"F= Full month named= Day (01-31)Y= 4-digit yearg:i A= Hour:Minutes AM/PM
{% if task.description %}- Only shows description section if description exists- Database field has
blank=True, null=Trueso it's optional
- Database field has
d-flex gap-2- Bootstrap flexbox with gap between buttons
Save the file
What This Does:
Shows a confirmation page before permanently deleting a task. This prevents accidental deletions.
Steps:
- Inside
tasks/templates/tasks/folder, createtask_confirm_delete.html - Copy and paste the following code:
{% extends 'tasks/base.html' %}
{% block title %}Delete Task - Task Manager{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card border-danger">
<div class="card-header bg-danger text-white">
<h3 class="mb-0">β οΈ Confirm Deletion</h3>
</div>
<div class="card-body">
<p class="lead">Are you sure you want to delete this task?</p>
<div class="alert alert-warning">
<strong>Task:</strong> {{ task.title }}
{% if task.description %}
<br><strong>Description:</strong> {{ task.description|truncatewords:15 }}
{% endif %}
</div>
<p class="text-muted">This action cannot be undone.</p>
<!-- Delete confirmation form -->
<form method="post" action="">
{% csrf_token %}
<div class="d-flex justify-content-between">
<button type="submit" class="btn btn-danger">
Yes, Delete Task
</button>
<a href="{% url 'task_list' %}" class="btn btn-secondary">
Cancel
</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}Understanding the Code:
border-danger,bg-danger- Red Bootstrap classes to indicate danger- Form with only CSRF token:
- No input fields needed
- Just confirmation via POST request
- GET request shows confirmation page
- POST request actually deletes the task
{{ task.description|truncatewords:15 }}- Shows first 15 words of description- Cancel button:
- Links back to task list
- Doesn't submit form, so task isn't deleted
Save the file
What to Expect:
Your app should now be fully functional! Let's test it.
Steps:
-
Make sure the development server is running:
python manage.py runserver
-
Open your browser and go to
http://127.0.0.1:8000/ -
Expected Result:
You should see a beautiful page with:- β Blue navigation bar with "π Task Manager" logo
- β Navigation links (All Tasks, Create Task, Admin Panel)
- β Message saying "No tasks yet!" with a "Create Task" button
- β Footer at the bottom
-
Test Creating a Task:
- Click "+ New Task" or "Create Task"
- You'll see a form with:
- Title field (text input)
- Description field (text area)
- Completed checkbox
- "Create Task" button
- "Cancel" button
- Fill in:
- Title: "My First Task"
- Description: "This is a test task to verify everything works!"
- Leave "Completed" unchecked
- Click "Create Task"
- Expected: Redirected to task list with green success message "Task created successfully!"
- Expected: Your task appears with:
- β³ Warning badge (because it's not completed)
- Title: "My First Task"
- Description (truncated to 20 words)
- Created date
- View, Edit, Delete buttons
-
Test Viewing a Task:
- Click "View" button
- Expected: See full task details including:
- Complete title and description
- Created timestamp
- Last updated timestamp
- Completed status badge
- Edit, Delete, Back to List buttons
-
Test Updating a Task:
- Click "Edit Task"
- Expected: Form pre-filled with existing data
- Change something (e.g., check "Completed")
- Click "Update Task"
- Expected: Redirected to task list with "Task updated!" message
- Expected: Task now shows β green badge and title is crossed out
-
Test Deleting a Task:
- Click "Delete" on any task
- Expected: Confirmation page with:
β οΈ Red warning header- Task details shown
- "This action cannot be undone" message
- "Yes, Delete Task" and "Cancel" buttons
- Click "Yes, Delete Task"
- Expected: Redirected to task list with "Task deleted!" message
- Expected: Task no longer appears in list
Common Issues:
| Issue | Solution |
|---|---|
| Templates not found | Check folder structure is exactly: tasks/templates/tasks/*.html |
| CSS/Styling not working | Check internet connection (Bootstrap loads from CDN) |
| Forms don't submit | Make sure you included {% csrf_token %} |
| URLs not working | Verify tasks app is in INSTALLED_APPS in settings.py |
| Messages not showing | Check base.html has the messages block |
What You've Built:
- β Complete CRUD functionality (Create, Read, Update, Delete)
- β Professional UI with Bootstrap
- β Template inheritance (DRY principle)
- β Dynamic URLs
- β Form validation
- β Success/error messages
- β Responsive design (works on mobile!)
Template Inheritance Summary:
base.html (parent template)
βββ Provides: navbar, footer, messages, styling
βββ Defines: {% block title %}, {% block content %}
β
βββ task_list.html extends base.html
β βββ Overrides: title, content (shows all tasks)
β
βββ task_form.html extends base.html
β βββ Overrides: title, content (create/update form)
β
βββ task_detail.html extends base.html
β βββ Overrides: title, content (single task view)
β
βββ task_confirm_delete.html extends base.html
βββ Overrides: title, content (deletion confirmation)
Django Template Syntax Quick Reference:
| Syntax | Purpose | Example |
|---|---|---|
{% %} |
Logic/Control Flow | {% if %} {% for %} {% block %} |
{{ }} |
Output Variables | {{ task.title }} |
{# #} |
Comments | {# This is a comment #} |
{% extends 'base.html' %} |
Template Inheritance | Inherit from parent template |
{% block name %}...{% endblock %} |
Define/Override Block | Create replaceable sections |
{% url 'name' %} |
Reverse URL Lookup | Generate URL from URL name |
{% url 'name' param %} |
URL with Parameter | Generate /task/5/ from name |
{% if condition %} |
Conditional | Show content if true |
{% for item in list %} |
Loop | Iterate over list |
{% csrf_token %} |
CSRF Security | Required in all POST forms |
{% load static %} |
Load Static Files | Enable {% static %} tag |
| `{{ var | filter }}` | Apply Filter |
| `{{ date | date:"M d, Y" }}` | Date Formatting |
| `{{ text | truncatewords:20 }}` | Truncate Text |
| `{{ text | upper }}` | Uppercase |
| `{{ text | lower }}` | Lowercase |
| `{{ number | add:5 }}` | Add to Number |
Edit tasks/admin.py:
from django.contrib import admin
from .models import Task
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_display = ['title', 'completed', 'created_at']
list_filter = ['completed', 'created_at']
search_fields = ['title', 'description']
list_editable = ['completed']Admin Customization:
list_display- Columns shown in list viewlist_filter- Add filter sidebarsearch_fields- Enable searchlist_editable- Edit directly in list viewreadonly_fields- Prevent editing certain fields
python manage.py createsuperuserFollow prompts:
- Username:
admin - Email:
admin@example.com(can be fake for development) - Password: Enter secure password (min 8 characters)
- Password confirmation: Re-enter password
What Is Superuser?
- Full access to admin panel
- Can create/edit/delete any data
- Manage users and permissions
- Access all registered models
π Congratulations! You've built a complete Django CRUD application!
What You Can Do RIGHT NOW:
Start the server if not running:
python manage.py runserverVisit: http://127.0.0.1:8000/
You Should See:
- β Beautiful blue navigation bar
- β "All Tasks" page
- β Ability to create, view, edit, and delete tasks
- β Success/error messages
- β Responsive design
Try These Actions:
- β Click "New Task" β Create a task
- β Click "View" β See task details
- β Click "Edit" β Update a task
- β Check "Completed" β See β badge and strikethrough
- β Click "Delete" β Confirm deletion
Visit: http://127.0.0.1:8000/admin/
Login with:
- Username: The username you just created (e.g.,
admin) - Password: The password you entered
After Login, You Should See:
- Django administration dashboard
- "TASKS" section with "Tasks" link
- Click on "Tasks" to manage tasks
Admin Features You Can Use:
| Feature | What It Does | Try It |
|---|---|---|
| List View | See all tasks in a table | Click "Tasks" |
| Quick Edit | Check/uncheck "Completed" without opening task | Toggle checkboxes in list |
| Add Task | Create new task | Click "ADD TASK +" button |
| Filter Sidebar | Filter by completed status or date | Use right sidebar filters |
| Search | Search tasks by title/description | Use search box at top |
| Edit Task | Click any task title | Modify and save |
| Delete Task | Select tasks β Actions dropdown β Delete | Bulk delete multiple tasks |
Cool Admin Features:
- β Auto-save timestamps - See exact created/updated times
- β Bulk actions - Delete multiple tasks at once
- β Filtering - Find tasks quickly
- β Search - Search across title and description
- β Direct editing - Toggle completion status from list view
You now have TWO interfaces to manage tasks:
| Feature | Public App | Admin Panel |
|---|---|---|
| Purpose | End-user interface | Management/admin interface |
| URL | http://127.0.0.1:8000/ |
http://127.0.0.1:8000/admin/ |
| Access | Anyone can visit | Requires admin login |
| Design | Custom Bootstrap UI | Django's built-in UI |
| Best For | Daily task management | Quick admin tasks, bulk operations |
| You Built | Everything from scratch! | Django provides automatically |
Checklist:
- Can create tasks in public app β
- Can view task list β
- Can update tasks β
- Can delete tasks β
- See success messages β
- Can login to admin panel β
- Can manage tasks in admin β
- Search works in admin β
- Filters work in admin β
- Tasks show in both interfaces β
Test Data Sync:
- Create a task in the public app
- Go to admin panel β Refresh β You should see the same task!
- Edit the task in admin panel
- Go back to public app β Refresh β See the changes!
This proves both interfaces use the same database! π―
What You've Accomplished:
- β Complete CRUD application with professional UI
- β Database-driven with SQLite
- β Form validation and error handling
- β URL routing with named patterns
- β Template inheritance (DRY principle)
- β Django admin panel customization
- β User authentication (superuser)
- β Success/error messaging system
- β Responsive Bootstrap design
- β Production-ready Django skills!
You Can Now:
- π Add more features (categories, due dates, priorities)
- π¨ Customize the design further
- π Add user authentication to public app
- π Deploy to a hosting platform
- π οΈ Build other Django applications using these skills!
python manage.py runserverAccess Your App:
- Main app: http://127.0.0.1:8000/
- Admin panel: http://127.0.0.1:8000/admin/
Stop Server: Press Ctrl+C in terminal
Django uses MTV architecture (similar to MVC):
- Model - Database layer (defines data structure)
- Template - Presentation layer (HTML with template tags)
- View - Business logic layer (processes requests)
Flow:
- User requests URL
- Django finds matching URL pattern
- Calls associated view function
- View queries database via models
- View renders template with data
- Django returns HTML response
- Define database structure using Python classes
- Each model class = one database table
- Each attribute = one database column
- ORM (Object-Relational Mapping) translates Python to SQL
Common Model Fields:
CharField(max_length=200) # Short text
TextField() # Long text
IntegerField() # Integer numbers
BooleanField(default=False) # True/False
DateTimeField(auto_now_add=True) # Timestamp
ForeignKey(OtherModel) # RelationshipQuerying Database:
Task.objects.all() # Get all tasks
Task.objects.get(pk=1) # Get one task by ID
Task.objects.filter(completed=True) # Get completed tasks
Task.objects.create(title="New") # Create new task- Functions that receive requests and return responses
- Process form data, query database, apply business logic
- Return rendered template or redirect
Function-Based Views:
def my_view(request):
# Process request
data = {'key': 'value'}
return render(request, 'template.html', data)- HTML files with Django template language
- Display dynamic content
- Includes template tags and filters
Template Syntax:
<!-- Variables -->
{{ variable_name }}
<!-- For loop -->
{% for item in items %}
{{ item.name }}
{% endfor %}
<!-- If statement -->
{% if condition %}
<!-- content -->
{% endif %}
<!-- URL linking -->
<a href="{% url 'url_name' %}">Link</a>
<a href="{% url 'url_name' object.pk %}">Link with parameter</a>- Handle user input securely
- Validate data automatically
- Render HTML form fields
Using Forms in Views:
# Display form (GET)
form = TaskForm()
# Process form (POST)
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('success_page')Form in Template:
<form method="post">
{% csrf_token %} <!-- Security token -->
{{ form.as_p }} <!-- Render form -->
<button type="submit">Submit</button>
</form>- Map URLs to view functions
- Extract parameters from URLs
- Name URLs for reverse lookup
URL with Parameter:
path('task/<int:pk>/', views.task_detail, name='task_detail')- Matches:
/task/5/,/task/123/ - Passes
pk=5orpk=123to view function
- Automatic admin interface
- Manage database through web interface
- Customizable for each model
Benefits:
- No need to build admin pages manually
- Full CRUD operations
- User-friendly interface
- Permission system built-in
- Version control for database schema
- Track model changes over time
- Apply changes safely
Migration Workflow:
- Change model in
models.py - Run
makemigrations(creates migration file) - Run
migrate(applies changes to database) - Migration file stored in version control
For static files, configure in settings.py:
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static']Use in templates:
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">- Display one-time notifications to users
- Success, error, warning, info messages
from django.contrib import messages
messages.success(request, 'Task created!')
messages.error(request, 'Something went wrong!')- Start server:
python manage.py runserver - Open browser: http://127.0.0.1:8000/
- You should see the Task List page
CREATE:
- Click "New Task" button
- Fill in title and description
- Click "Create Task"
- Verify redirect to task list with success message
READ:
- View all tasks on homepage
- Click "View" on any task
- See complete task details
UPDATE:
- Click "Edit" on any task
- Modify fields
- Click "Update Task"
- Verify changes saved
DELETE:
- Click "Delete" on any task
- Confirm deletion on confirmation page
- Verify task removed from list
- Go to http://127.0.0.1:8000/admin/
- Login with superuser credentials
- Click "Tasks" under "TASKS" section
- Create, edit, delete tasks through admin interface
- Test filters and search functionality
Problem: Activate.ps1 script error or "running scripts is disabled on this system"
Solution: Set PowerShell execution policy. You have two options:
Option 1: Current User Only (Recommended)
# Open PowerShell as Administrator
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserOption 2: All Users
# Open PowerShell as Administrator
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachineOr use Command Prompt instead:
myenv\Scripts\activate.batπ See Step 3 for detailed instructions with screenshots and troubleshooting
Problem: ModuleNotFoundError: No module named 'django'
Solution:
- Make sure virtual environment is activated (you see
(myenv)) - Reinstall Django:
pip install django
Problem: Database conflicts or migration issues
Solution:
# Delete db.sqlite3 file
# Delete migrations folder contents except __init__.py
python manage.py makemigrations
python manage.py migrateProblem: TemplateDoesNotExist
Solution:
- Check template path:
tasks/templates/tasks/template_name.html - Verify app is in
INSTALLED_APPS - Check template name in view matches file name
Problem: CSS/JS not working
Solution:
- Add
{% load static %}at top of template - Use
{% static 'path/to/file' %} - Run
python manage.py collectstaticfor production
Problem: Port 8000 already in use
Solution:
# Use different port
python manage.py runserver 8080
# Or find and stop process using port 8000-
Add User Authentication
- User registration/login
- User-specific tasks
- Permissions
-
Improve UI
- Custom CSS
- JavaScript interactions
- Better responsive design
-
Add Features
- Task categories
- Due dates
- Priority levels
- Task assignments
- File attachments
-
Testing
- Write unit tests
- Integration tests
- Test coverage
-
Deployment
- Deploy to Heroku/PythonAnywhere
- Configure production settings
- Use PostgreSQL instead of SQLite
- Official Django Documentation: https://docs.djangoproject.com/
- Django Tutorial: https://docs.djangoproject.com/en/stable/intro/tutorial01/
- Django Girls Tutorial: https://tutorial.djangogirls.org/
You've successfully built a complete Django CRUD application! You learned:
β
Setting up Python virtual environment
β
Installing and configuring Django
β
Creating Django projects and apps
β
Defining database models
β
Creating and applying migrations
β
Building forms for user input
β
Creating views for business logic
β
Configuring URL routing
β
Building templates with Django template language
β
Customizing Django admin panel
β
Implementing full CRUD operations
β
Using Django's messages framework
β
Understanding MTV architecture
Congratulations! You now have a solid foundation in Django development. Keep building and exploring! π