Introduction
Django is a powerful, high-level Python web framework that enables rapid development of secure and maintainable websites. Built by experienced developers, Django takes care of much of the hassle of web development, so you can focus on writing your app without needing to reinvent the wheel.
This comprehensive beginner tutorial will walk you through creating your first Django project from scratch. You'll learn how to set up Django, create apps, work with models, views, templates, and build a functional web application. By the end of this tutorial, you'll have a solid foundation in Django development.
Prerequisites
Before starting, make sure you have:
- Python 3.8+ installed on your system
- pip (Python package installer)
- Basic Python knowledge (variables, functions, classes)
- A code editor (VS Code, PyCharm, or any editor you prefer)
- Terminal/Command Prompt access
You can check your Python version by running:
bash
python --version
# or
python3 --version
Step 1: Setting Up Your Environment
First, let's create a virtual environment to isolate your Django project dependencies:
On Windows (PowerShell):
bash
# Create project directory
mkdir django-blog
cd django-blog
# Create virtual environment
py -m venv venv
# Activate virtual environment
venv\Scripts\Activate.ps1
On macOS/Linux:
bash
# Create project directory
mkdir django-blog
cd django-blog
# Create virtual environment
python3 -m venv venv
# Activate virtual environment
source venv/bin/activate
Once activated, you'll see (venv) in your terminal prompt. This indicates your virtual environment is active.
Install Django:
bash
pip install django
# Verify installation
python -m django --version
You should see the Django version number (e.g., 4.2.x or 5.0.x).
Step 2: Creating Your First Django Project
Now let's create a new Django project:
bash
# Create Django project
django-admin startproject myblog
# Navigate into project directory
cd myblog
This creates a myblog directory with the following structure:
myblog/
manage.py
myblog/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
Understanding the Project Structure:
manage.py: Django's command-line utility for administrative tasks
settings.py: Configuration file for your Django project
urls.py: URL routing configuration
wsgi.py: WSGI configuration for deployment
asgi.py: ASGI configuration for async support
Run the Development Server:
bash
python manage.py runserver
Open your browser and visit http://127.0.0.1:8000/. You should see the Django welcome page!
Step 3: Creating Your First Django App
In Django, a project contains multiple apps. Each app handles a specific functionality. Let's create a blog app:
bash
# Create a new app
python manage.py startapp blog
This creates a blog directory with:
blog/
__init__.py
admin.py
apps.py
models.py
tests.py
views.py
migrations/
Understanding App Structure:
models.py: Define your database models
views.py: Handle request/response logic
admin.py: Register models for Django admin
urls.py: URL routing for the app (create this)
migrations/: Database migration files
Register the App:
Open myblog/settings.py and add your app to INSTALLED_APPS:
python
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # Add your app here
]
Step 4: Creating Your First Model
Models define the structure of your database. Let's create a Post model for our blog:
Open blog/models.py and add:
python
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
status = models.CharField(
max_length=10,
choices=[('draft', 'Draft'), ('published', 'Published')],
default='draft'
)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
def publish(self):
self.published_at = timezone.now()
self.status = 'published'
self.save()
Understanding the Model:
CharField: For short text fields (title, status)
TextField: For longer text content
SlugField: URL-friendly version of title
ForeignKey: Relationship to User model
DateTimeField: For dates and times
auto_now_add: Set when object is created
auto_now: Update on every save
Create and Apply Migrations:
bash
# Create migration files
python manage.py makemigrations
# Apply migrations to database
python manage.py migrate
This creates the database tables for your models.
Step 5: Working with Django Admin
Django provides a powerful admin interface. Let's register our Post model:
Open blog/admin.py:
python
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'status', 'created_at', 'published_at']
list_filter = ['status', 'created_at', 'published_at']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'created_at'
ordering = ['-created_at']
Create a Superuser:
bash
python manage.py createsuperuser
Follow the prompts to create an admin user. Then run the server and visit http://127.0.0.1:8000/admin/ to access the admin panel.
Admin Features:
list_display: Fields to display in list view
list_filter: Filters in the sidebar
search_fields: Searchable fields
prepopulated_fields: Auto-generate slug from title
date_hierarchy: Date-based navigation
ordering: Default ordering
Step 6: Creating Views
Views handle the request/response logic. Let's create views for listing and viewing posts:
Open blog/views.py:
python
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from .models import Post
def post_list(request):
posts = Post.objects.filter(
status='published',
published_at__lte=timezone.now()
).order_by('-published_at')
context = {
'posts': posts
}
return render(request, 'blog/post_list.html', context)
def post_detail(request, slug):
post = get_object_or_404(
Post,
slug=slug,
status='published',
published_at__lte=timezone.now()
)
context = {
'post': post
}
return render(request, 'blog/post_detail.html', context)
Understanding Views:
post_list: Displays all published posts
post_detail: Shows a single post by slug
get_object_or_404: Returns 404 if post not found
render: Renders template with context
objects.filter(): QuerySet filtering
published_at__lte: Less than or equal filter
Class-Based Views (Alternative):
python
from django.views.generic import ListView, DetailView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
def get_queryset(self):
return Post.objects.filter(
status='published',
published_at__lte=timezone.now()
).order_by('-published_at')
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
slug_field = 'slug'
slug_url_kwarg = 'slug'
Step 7: Creating Templates
Templates define the HTML structure. Create a templates directory:
bash
# In your blog app directory
mkdir -p blog/templates/blog
Create blog/templates/blog/post_list.html:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blog Posts</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.post {
border-bottom: 1px solid #ddd;
padding: 20px 0;
}
.post h2 a {
color: #333;
text-decoration: none;
}
.post h2 a:hover {
color: #007bff;
}
.meta {
color: #666;
font-size: 0.9em;
}
</style>
</head>
<body>
<h1>My Blog</h1>
{% for post in posts %}
<div class="post">
<h2><a href="{% url 'post_detail' post.slug %}">{{ post.title }}</a></h2>
<p class="meta">
Published on {{ post.published_at|date:"F d, Y" }} by {{ post.author.username }}
</p>
<p>{{ post.content|truncatewords:30 }}</p>
<a href="{% url 'post_detail' post.slug %}">Read more →</a>
</div>
{% empty %}
<p>No posts available.</p>
{% endfor %}
</body>
</html>
Create blog/templates/blog/post_detail.html:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ post.title }}</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
.meta {
color: #666;
font-size: 0.9em;
margin-bottom: 20px;
}
.content {
margin-top: 20px;
}
a {
color: #007bff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<a href="{% url 'post_list' %}">← Back to Posts</a>
<h1>{{ post.title }}</h1>
<p class="meta">
Published on {{ post.published_at|date:"F d, Y" }} by {{ post.author.username }}
</p>
<div class="content">
{{ post.content|linebreaks }}
</div>
</body>
</html>
Django Template Tags:
{% for %}: Loop through items
{% if %}: Conditional statements
{% url %}: Generate URLs
{{ variable }}: Display variables
|filter: Apply template filters
|date: Format dates
|truncatewords: Truncate text
|linebreaks: Convert line breaks to HTML
Step 8: Setting Up URLs
URLs map URLs to views. Create blog/urls.py:
python
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.post_list, name='post_list'),
path('<slug:slug>/', views.post_detail, name='post_detail'),
]
Update myblog/urls.py:
python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
]
Understanding URLs:
path(): Define URL patterns
<slug:slug>: Capture slug parameter
include(): Include app URLs
app_name: Namespace for URLs
name: Name for URL reversing
Test Your URLs:
bash
python manage.py runserver
Visit:
http://127.0.0.1:8000/ - Post list
http://127.0.0.1:8000/admin/ - Admin panel
http://127.0.0.1:8000/your-post-slug/ - Post detail
Django makes form handling easy. Create blog/forms.py:
python
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'content', 'status']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'slug': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
'status': forms.Select(attrs={'class': 'form-control'}),
}
Create a View for the Form:
python
# In blog/views.py
from django.shortcuts import redirect
from django.contrib.auth.decorators import login_required
from .forms import PostForm
@login_required
def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('post_detail', slug=post.slug)
else:
form = PostForm()
return render(request, 'blog/post_form.html', {'form': form})
Create Template for Form:
html
<!-- blog/templates/blog/post_form.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Create Post</title>
</head>
<body>
<h1>Create New Post</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Create Post</button>
</form>
</body>
</html>
Add URL:
python
# In blog/urls.py
path('create/', views.post_create, name='post_create'),
Form Features:
ModelForm: Auto-generate form from model
widgets: Customize form fields
csrf_token: CSRF protection
is_valid(): Validate form data
save(commit=False): Save without committing
Django handles static files (CSS, JS, images) and media files (user uploads).
Configure Static Files in settings.py:
python
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
Update myblog/urls.py for Development:
python
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
]
# Serve media files in development
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Create Static Files Directory:
bash
mkdir -p blog/static/blog/css
Create blog/static/blog/css/style.css:
css
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.post {
background: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
Load Static Files in Templates:
html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{% static 'blog/css/style.css' %}">
</head>
<body>
<!-- Your content -->
</body>
</html>
Step 11: Database Queries and ORM
Django ORM provides powerful database querying. Here are common operations:
Basic Queries:
python
# Get all posts
all_posts = Post.objects.all()
# Get a single post
post = Post.objects.get(id=1)
# Filter posts
published_posts = Post.objects.filter(status='published')
# Exclude posts
draft_posts = Post.objects.exclude(status='published')
# Order posts
ordered_posts = Post.objects.order_by('-created_at')
# Limit results
recent_posts = Post.objects.all()[:5]
# Count posts
post_count = Post.objects.count()
# Check if exists
exists = Post.objects.filter(title='My Post').exists()
Advanced Queries:
python
# Complex filtering
posts = Post.objects.filter(
status='published',
created_at__gte=timezone.now() - timedelta(days=7)
)
# Lookups
posts = Post.objects.filter(title__icontains='django')
posts = Post.objects.filter(created_at__year=2024)
posts = Post.objects.filter(author__username='admin')
# Q objects for complex queries
from django.db.models import Q
posts = Post.objects.filter(
Q(title__icontains='django') | Q(content__icontains='python')
)
# Aggregations
from django.db.models import Count, Avg
Post.objects.aggregate(Count('id'))
Post.objects.filter(status='published').count()
# Annotations
from django.db.models import Count
posts = Post.objects.annotate(
comment_count=Count('comments')
)
Query Optimization:
python
# select_related (for ForeignKey)
posts = Post.objects.select_related('author').all()
# prefetch_related (for ManyToMany, reverse ForeignKey)
posts = Post.objects.prefetch_related('tags').all()
# only() - fetch only specified fields
posts = Post.objects.only('title', 'created_at')
# defer() - exclude specified fields
posts = Post.objects.defer('content')
Step 12: User Authentication
Django includes a built-in authentication system. Let's add login/logout functionality:
Create blog/templates/blog/login.html:
html
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Login</button>
</form>
</body>
</html>
Add Login View:
python
# In blog/views.py
from django.contrib.auth import login
from django.contrib.auth.forms import AuthenticationForm
from django.shortcuts import redirect
def login_view(request):
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
user = form.get_user()
login(request, user)
return redirect('post_list')
else:
form = AuthenticationForm()
return render(request, 'blog/login.html', {'form': form})
Add URLs:
python
# In blog/urls.py
from django.contrib.auth import views as auth_views
urlpatterns = [
path('login/', views.login_view, name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
# ... other URLs
]
Protect Views:
python
from django.contrib.auth.decorators import login_required
@login_required
def post_create(request):
# Only logged-in users can access
pass
Check Authentication in Templates:
html
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
<a href="{% url 'logout' %}">Logout</a>
{% else %}
<a href="{% url 'login' %}">Login</a>
{% endif %}
Step 13: Testing Your Application
Django includes a built-in testing framework. Create tests in blog/tests.py:
python
from django.test import TestCase, Client
from django.contrib.auth.models import User
from django.utils import timezone
from .models import Post
class PostModelTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
self.post = Post.objects.create(
title='Test Post',
slug='test-post',
content='Test content',
author=self.user,
status='published',
published_at=timezone.now()
)
def test_post_str(self):
self.assertEqual(str(self.post), 'Test Post')
def test_post_publish(self):
self.post.publish()
self.assertEqual(self.post.status, 'published')
self.assertIsNotNone(self.post.published_at)
class PostViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
self.post = Post.objects.create(
title='Test Post',
slug='test-post',
content='Test content',
author=self.user,
status='published',
published_at=timezone.now()
)
def test_post_list_view(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test Post')
def test_post_detail_view(self):
response = self.client.get('/test-post/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test content')
Run Tests:
bash
# Run all tests
python manage.py test
# Run specific test
python manage.py test blog.tests.PostModelTest
# Run with verbosity
python manage.py test --verbosity=2
Step 14: Deployment Preparation
Before deploying, configure your project for production:
Update settings.py for Production:
python
# Security settings
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# Database (use PostgreSQL in production)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'your_db_name',
'USER': 'your_db_user',
'PASSWORD': 'your_db_password',
'HOST': 'localhost',
'PORT': '5432',
}
}
# Static files
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
# Security middleware
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
Collect Static Files:
bash
python manage.py collectstatic
Create requirements.txt:
bash
pip freeze > requirements.txt
Common Deployment Platforms:
- Heroku: Easy deployment with Git
- DigitalOcean: VPS with Django setup
- AWS: Elastic Beanstalk or EC2
- PythonAnywhere: Django hosting
- Railway: Modern deployment platform
Deployment Checklist:
- ✅ Set
DEBUG = False
- ✅ Configure
ALLOWED_HOSTS
- ✅ Set up production database
- ✅ Configure static files
- ✅ Set up environment variables
- ✅ Enable HTTPS
- ✅ Configure security settings
- ✅ Set up error logging
- ✅ Configure email settings
Common Django Commands Cheat Sheet
Here's a quick reference for common Django commands:
Project Management:
bash
# Create project
django-admin startproject projectname
# Create app
python manage.py startapp appname
# Run development server
python manage.py runserver
# Run on specific port
python manage.py runserver 8080
Database Management:
bash
# Create migrations
python manage.py makemigrations
# Apply migrations
python manage.py migrate
# Show migration status
python manage.py showmigrations
# Rollback migration
python manage.py migrate appname migration_number
# Create superuser
python manage.py createsuperuser
Shell and Database:
bash
# Open Django shell
python manage.py shell
# Open shell with IPython
python manage.py shell -i ipython
# Dump data
python manage.py dumpdata appname > data.json
# Load data
python manage.py loaddata data.json
Static Files:
bash
# Collect static files
python manage.py collectstatic
# Find static files
python manage.py findstatic css/style.css
Testing:
bash
# Run all tests
python manage.py test
# Run specific app tests
python manage.py test blog
# Run with coverage
coverage run --source='.' manage.py test
coverage report
Other Useful Commands:
bash
# Check for issues
python manage.py check
# Show URL patterns
python manage.py show_urls
# Create custom management command
python manage.py startapp appname
# Then create appname/management/commands/commandname.py
Next Steps and Learning Resources
Congratulations! You've built your first Django application. Here's what to learn next:
Advanced Topics:
- REST APIs: Use Django REST Framework - Check out our Django REST Framework Tutorial
- Real-time Features: WebSockets with Django Channels
- Caching: Redis and Memcached integration
- Celery: Background task processing
- Search: Full-text search with Elasticsearch
- API Integration: Third-party API integration
- Advanced Queries: Complex database queries
- Custom Managers: Custom QuerySets
- Signals: Django signals for events
- Middleware: Custom middleware
Best Practices:
- Project Structure: Organize large projects
- Code Organization: Follow Django conventions
- Security: Implement security best practices
- Performance: Optimize queries and caching
- Testing: Write comprehensive tests
- Documentation: Document your code
Learning Resources:
- Official Django Documentation: https://docs.djangoproject.com/
- Django Tutorial: Official tutorial on Django website
- Django for Beginners: Book by William S. Vincent
- Two Scoops of Django: Best practices book
- Django Girls Tutorial: Beginner-friendly tutorial
- Real Python: Django tutorials and articles
Community:
- Django Forum: Community discussions
- Stack Overflow: Q&A platform
- Django Discord: Real-time chat
- Reddit r/django: Community discussions
Conclusion
You've successfully created your first Django project! You've learned:
- ✅ Setting up Django environment
- ✅ Creating projects and apps
- ✅ Working with models and databases
- ✅ Creating views and templates
- ✅ URL routing
- ✅ Django admin interface
- ✅ Forms and user input
- ✅ Static files
- ✅ User authentication
- ✅ Testing
- ✅ Deployment preparation
Key Takeaways:
- Django follows the "batteries-included" philosophy
- Models define your database structure
- Views handle request/response logic
- Templates render HTML
- URLs map URLs to views
- Django admin provides powerful admin interface
- ORM makes database queries easy
- Testing is built-in and encouraged
Remember:
- Practice is key to mastering Django
- Read the official documentation
- Join the Django community
- Build projects to learn
- Follow Django best practices
- Keep learning and improving
Django is a powerful framework that can help you build complex web applications efficiently. Continue practicing, building projects, and exploring advanced features. Happy coding!