Week 2: Django Models & Admin
Week 2: Django Models & Admin
Week 2: Django Models & Admin
Professional Django Developer Bootcamp
Learning Objectives
By the end of this week, students will be able to:
- Design and implement Django models with various field types
- Understand Django's ORM and database relationships
- Create and apply database migrations
- Configure and customize Django's admin interface
- Connect local Git repository to GitHub
- Build a complete blog application with Post and Comment models
- Manage database records through the admin panel
Day 1: Introduction to Django Models & ORM
Session 1: Understanding Django's ORM (2 hours)
What is an ORM?
Object-Relational Mapping (ORM) bridges the gap between Python objects and database tables:
- Without ORM: Write raw SQL queries
- With ORM: Use Python methods and attributes
# Raw SQL (what we avoid)
cursor.execute("SELECT * FROM blog_post WHERE published=True")
# Django ORM (what we use)
Post.objects.filter(published=True)Django Model Basics
A Django model is a Python class that represents a database table:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.titleThis creates a database table with columns for title, content, and created_at.
Key Model Field Types:
# Text fields
title = models.CharField(max_length=100) # Short text
content = models.TextField() # Long text
slug = models.SlugField(max_length=100) # URL-friendly text
# Number fields
views = models.IntegerField(default=0) # Whole numbers
rating = models.FloatField() # Decimal numbers
# Date/Time fields
created_at = models.DateTimeField(auto_now_add=True) # Set once
updated_at = models.DateTimeField(auto_now=True) # Update always
publish_date = models.DateField() # Date only
# Boolean fields
published = models.BooleanField(default=False)
# Choice fields
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
]
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')Recommended Videos:
- Django Models Explained (15 min)
- Django ORM Tutorial (22 min)
Session 2: Creating Your First Models (2 hours)
Let's create a new Django project for our blog:
# Create new project
django-admin startproject blog_project
cd blog_project
# Create blog app
python manage.py startapp blog
# Activate virtual environment (if not already)
source django_bootcamp_env/bin/activate # macOS/Linux
# or
django_bootcamp_env\Scripts\activate # WindowsStep 1: Configure Settings
In blog_project/settings.py:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # Add your blog app
]Step 2: Create Blog Models
In blog/models.py:
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils import timezone
class Post(models.Model):
"""Blog post model"""
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
content = models.TextField()
excerpt = models.TextField(max_length=300, blank=True, help_text="Brief description")
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
class Meta:
ordering = ['-created_at'] # Newest first
verbose_name = 'Blog Post'
verbose_name_plural = 'Blog Posts'
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
# Auto-set published_at when status changes to published
if self.status == 'published' and not self.published_at:
self.published_at = timezone.now()
super().save(*args, **kwargs)
class Comment(models.Model):
"""Comment model for blog posts"""
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
name = models.CharField(max_length=100)
email = models.EmailField()
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
is_approved = models.BooleanField(default=False)
class Meta:
ordering = ['created_at']
def __str__(self):
return f'Comment by {self.name} on {self.post.title}'Understanding Model Relationships:
# ForeignKey: Many-to-One relationship
author = models.ForeignKey(User, on_delete=models.CASCADE)
# Many posts can belong to one user
# related_name allows reverse lookups
user.posts.all() # Get all posts by this userRecommended Videos:
- Django Model Fields Explained (18 min)
- Django ForeignKey Relationships (12 min)
Day 2: Database Migrations
Session 3: Understanding Django Migrations (2 hours)
What are Migrations?
Migrations are Django's way of propagating changes to your models into your database schema:
Model Changes → Migration Files → Database Schema UpdatesMigration Commands:
# Create migration files for model changes
python manage.py makemigrations
# Apply migrations to database
python manage.py migrate
# View migration status
python manage.py showmigrations
# View SQL that will be executed
python manage.py sqlmigrate blog 0001Step 1: Create Initial Migration
# Create migrations for our blog models
python manage.py makemigrations blog
# You'll see output like:
# Migrations for 'blog':
# blog/migrations/0001_initial.py
# - Create model Post
# - Create model CommentStep 2: Apply Migrations
# Apply all pending migrations
python manage.py migrate
# You'll see output confirming table creation:
# Operations to perform:
# Apply all migrations: admin, auth, contenttypes, sessions, blog
# Running migrations:
# Applying blog.0001_initial... OKMigration File Structure:
Django creates migration files in blog/migrations/0001_initial.py:
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Post',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True)),
('title', models.CharField(max_length=200)),
('content', models.TextField()),
# ... more fields
],
),
# ... more operations
]Common Migration Scenarios:
# After adding a new field to a model
python manage.py makemigrations
python manage.py migrate
# After changing a field type
python manage.py makemigrations
python manage.py migrate
# View what migrations will do without applying them
python manage.py migrate --planRecommended Videos:
- Django Migrations Explained (16 min)
- Django Migration Best Practices (20 min)
Day 3: Django Admin Interface
Session 4: Setting Up Django Admin (2 hours)
Create Superuser Account:
# Create an admin user
python manage.py createsuperuser
# Follow the prompts:
# Username: admin
# Email address: admin@example.com
# Password: (choose a secure password)Step 1: Register Models in Admin
In blog/admin.py:
from django.contrib import admin
from .models import Post, Comment
# Basic registration
admin.site.register(Post)
admin.site.register(Comment)Step 2: Start Server and Access Admin
# Run development server
python manage.py runserver
# Visit: http://127.0.0.1:8000/admin/
# Login with your superuser credentialsStep 3: Customize Admin Interface
Enhanced blog/admin.py:
from django.contrib import admin
from .models import Post, Comment
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'status', 'created_at']
list_filter = ['status', 'created_at', 'author']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'created_at'
ordering = ['-created_at']
list_editable = ['status']
# Organize form fields
fieldsets = (
('Post Content', {
'fields': ('title', 'slug', 'content', 'excerpt')
}),
('Metadata', {
'fields': ('author', 'status', 'published_at'),
'classes': ('collapse',)
}),
)
# Filter by current user for non-superusers
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ['name', 'post', 'created_at', 'is_approved']
list_filter = ['is_approved', 'created_at']
search_fields = ['name', 'email', 'content']
date_hierarchy = 'created_at'
list_editable = ['is_approved']
readonly_fields = ['created_at']
# Custom action to approve comments
actions = ['approve_comments']
def approve_comments(self, request, queryset):
queryset.update(is_approved=True)
self.message_user(request, f"Approved {queryset.count()} comments.")
approve_comments.short_description = "Approve selected comments"
# Customize admin site headers
admin.site.site_header = "Blog Administration"
admin.site.site_title = "Blog Admin Portal"
admin.site.index_title = "Welcome to Blog Administration"Admin Interface Features:
- list_display: Columns shown in the list view
- list_filter: Filter sidebar options
- search_fields: Fields searchable via search box
- prepopulated_fields: Auto-populate slug from title
- list_editable: Edit fields directly in list view
- fieldsets: Organize form fields into sections
Recommended Videos:
- Django Admin Customization (25 min)
- Django Admin Best Practices (18 min)
Day 4: GitHub Integration & Advanced Git
Session 5: Connecting to GitHub (2 hours)
Step 1: Create GitHub Repository
- Go to GitHub.com and sign in
- Click "New repository"
- Name it
django-blog-bootcamp - Keep it public for the course
- Don't initialize with README (we have local files)
Step 2: Connect Local Repository to GitHub
# Initialize Git if you haven't already
git init
# Add all files to staging
git add .
# Create initial commit
git commit -m "Initial blog project setup
- Create Django blog project with Post and Comment models
- Configure admin interface with custom settings
- Set up database migrations
- Add comprehensive model fields and relationships"
# Add GitHub remote repository
git remote add origin https://github.com/YOUR_USERNAME/django-blog-bootcamp.git
# Push to GitHub (first time)
git push -u origin mainStep 3: Verify GitHub Connection
# Check remote repositories
git remote -v
# You should see:
# origin https://github.com/YOUR_USERNAME/django-blog-bootcamp.git (fetch)
# origin https://github.com/YOUR_USERNAME/django-blog-bootcamp.git (push)GitHub Best Practices:
- Meaningful commit messages: Describe what and why
- Regular commits: Don't wait until everything is perfect
- Ignore sensitive files: Use
.gitignore
Create .gitignore file:
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
# Django
*.log
local_settings.py
db.sqlite3
media/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.dbRecommended Videos:
- Git and GitHub for Beginners (40 min)
- GitHub Workflow for Django Projects (15 min)
Day 5: Blog Application Development
Project 2: Complete Personal Blog
Step 1: Create Blog Views
In blog/views.py:
from django.shortcuts import render, get_object_or_404
from django.core.paginator import Paginator
from .models import Post, Comment
def post_list(request):
"""Display list of published posts"""
posts = Post.objects.filter(status='published').order_by('-published_at')
# Pagination - 5 posts per page
paginator = Paginator(posts, 5)
page_number = request.GET.get('page')
posts = paginator.get_page(page_number)
context = {
'posts': posts,
'title': 'Latest Blog Posts'
}
return render(request, 'blog/post_list.html', context)
def post_detail(request, slug):
"""Display individual post with comments"""
post = get_object_or_404(Post, slug=slug, status='published')
comments = post.comments.filter(is_approved=True)
context = {
'post': post,
'comments': comments,
'title': post.title
}
return render(request, 'blog/post_detail.html', context)
def about(request):
"""About the blog"""
return render(request, 'blog/about.html', {'title': 'About'})Step 2: Configure URLs
Create blog/urls.py:
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<slug:slug>/', views.post_detail, name='post_detail'),
path('about/', views.about, name='about'),
]Update blog_project/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
]Step 3: Create Templates
Create directory structure:
blog/
└── templates/
└── blog/
├── base.html
├── post_list.html
├── post_detail.html
└── about.htmlBase template (blog/templates/blog/base.html):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Django Blog{% endblock %}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f4f4f4;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: white;
min-height: 100vh;
}
header {
background: #2c3e50;
color: white;
padding: 1rem;
margin-bottom: 2rem;
border-radius: 5px;
}
nav a {
color: white;
text-decoration: none;
margin-right: 1rem;
padding: 0.5rem;
border-radius: 3px;
transition: background 0.3s;
}
nav a:hover {
background: rgba(255, 255, 255, 0.2);
}
h1 { color: #2c3e50; margin-bottom: 1rem; }
h2 { color: #34495e; margin: 1.5rem 0 1rem; }
.post-meta {
color: #7f8c8d;
font-size: 0.9em;
margin-bottom: 1rem;
}
.post-excerpt {
color: #555;
margin-bottom: 1rem;
}
.post-link {
color: #3498db;
text-decoration: none;
font-weight: bold;
}
.post-link:hover {
text-decoration: underline;
}
.comment {
background: #ecf0f1;
padding: 1rem;
margin: 1rem 0;
border-radius: 5px;
border-left: 4px solid #3498db;
}
.pagination {
text-align: center;
margin: 2rem 0;
}
.pagination a {
color: #3498db;
padding: 0.5rem 1rem;
text-decoration: none;
margin: 0 0.25rem;
border-radius: 3px;
}
.pagination a:hover {
background: #3498db;
color: white;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>My Django Blog</h1>
<nav>
<a href="{% url 'blog:post_list' %}">Home</a>
<a href="{% url 'blog:about' %}">About</a>
<a href="{% url 'admin:index' %}">Admin</a>
</nav>
</header>
<main>
{% block content %}
{% endblock %}
</main>
</div>
</body>
</html>Post list template (blog/templates/blog/post_list.html):
{% extends 'blog/base.html' %}
{% block title %}{{ title }} - My Django Blog{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
{% for post in posts %}
<article style="border-bottom: 1px solid #eee; margin-bottom: 2rem; padding-bottom: 1rem;">
<h2>
<a href="{{ post.get_absolute_url }}" class="post-link">
{{ post.title }}
</a>
</h2>
<div class="post-meta">
By {{ post.author.get_full_name|default:post.author.username }} on
{{ post.published_at|date:"F d, Y" }}
• {{ post.comments.count }} comment{{ post.comments.count|pluralize }}
</div>
<div class="post-excerpt">
{% if post.excerpt %}
{{ post.excerpt }}
{% else %}
{{ post.content|truncatewords:30 }}
{% endif %}
</div>
<a href="{{ post.get_absolute_url }}" class="post-link">Read more →</a>
</article>
{% empty %}
<p>No blog posts yet. Check back soon!</p>
{% endfor %}
<!-- Pagination -->
{% if posts.has_other_pages %}
<div class="pagination">
{% if posts.has_previous %}
<a href="?page=1">« First</a>
<a href="?page={{ posts.previous_page_number }}">‹ Previous</a>
{% endif %}
<span>Page {{ posts.number }} of {{ posts.paginator.num_pages }}</span>
{% if posts.has_next %}
<a href="?page={{ posts.next_page_number }}">Next ›</a>
<a href="?page={{ posts.paginator.num_pages }}">Last »</a>
{% endif %}
</div>
{% endif %}
{% endblock %}Post detail template (blog/templates/blog/post_detail.html):
{% extends 'blog/base.html' %}
{% block title %}{{ post.title }} - My Django Blog{% endblock %}
{% block content %}
<article>
<h1>{{ post.title }}</h1>
<div class="post-meta">
By {{ post.author.get_full_name|default:post.author.username }} on
{{ post.published_at|date:"F d, Y" }}
{% if post.updated_at > post.created_at %}
(Updated: {{ post.updated_at|date:"F d, Y" }})
{% endif %}
</div>
<div style="margin: 2rem 0; line-height: 1.8;">
{{ post.content|linebreaks }}
</div>
</article>
<hr style="margin: 3rem 0;">
<section>
<h2>Comments ({{ comments.count }})</h2>
{% for comment in comments %}
<div class="comment">
<h4>{{ comment.name }}</h4>
<small class="post-meta">{{ comment.created_at|date:"F d, Y \a\t g:i A" }}</small>
<p>{{ comment.content|linebreaks }}</p>
</div>
{% empty %}
<p>No comments yet. Be the first to comment!</p>
{% endfor %}
</section>
<p style="margin-top: 2rem;">
<a href="{% url 'blog:post_list' %}" class="post-link">← Back to all posts</a>
</p>
{% endblock %}Step 4: Add Sample Data via Admin
- Run the server:
python manage.py runserver - Go to admin:
http://127.0.0.1:8000/admin/ - Create 2-3 blog posts with status "Published"
- Add some comments to the posts
- Test your blog at
http://127.0.0.1:8000/
Recommended Videos:
- Django Templates Deep Dive (30 min)
- Django Pagination Tutorial (15 min)
Week 2 Assignment
Requirements:
- ✅ Create a Django blog project with Post and Comment models
- ✅ Implement proper model relationships using ForeignKey
- ✅ Create and apply database migrations
- ✅ Set up and customize Django admin interface
- ✅ Connect your project to a public GitHub repository
- ✅ Build a functional blog with post list and detail views
- ✅ Add pagination to the post list
- ✅ Style your templates with CSS
Submission Checklist:
- [ ] Push your complete blog project to GitHub
- [ ] Include at least 3 sample blog posts via admin
- [ ] Demonstrate working admin interface
- [ ] Show functional blog frontend
- [ ] Include meaningful commit messages
- [ ] Create a README.md with setup instructions
Bonus Challenges:
- Add a search functionality for blog posts
- Create a contact form model
- Add categories or tags to blog posts
- Implement a comment form on the frontend
- Add user profile pages
GitHub Repository Structure:
django-blog-bootcamp/
├── blog_project/
│ ├── blog_project/
│ ├── blog/
│ ├── manage.py
│ └── db.sqlite3
├── .gitignore
├── requirements.txt
└── README.mdSample README.md:
# Django Blog Bootcamp - Week 2
A personal blog application built with Django as part of the Professional Django Developer Bootcamp.
## Features
- Blog post management
- Comment system
- Django admin interface
- Responsive design
- Pagination
## Setup Instructions
1. Clone the repository
2. Create virtual environment: `python -m venv venv`
3. Activate virtual environment
4. Install dependencies: `pip install -r requirements.txt`
5. Run migrations: `python manage.py migrate`
6. Create superuser: `python manage.py createsuperuser`
7. Start server: `python manage.py runserver`
## Technologies Used
- Django 4.x
- PostgreSQL (production)
- SQLite (development)
- HTML/CSS
- Git/GitHubWeek 2 Summary
Excellent progress! You've now mastered Django's data layer and built a complete blog application:
✅ Django Models: Created complex models with various field types
✅ Database Relationships: Implemented ForeignKey relationships
✅ Django ORM: Learned to query and manipulate data with Python
✅ Migrations: Managed database schema changes safely
✅ Admin Interface: Customized Django's powerful admin panel
✅ GitHub Integration: Connected your project to version control
✅ Complete Blog: Built a functional blog with posts and comments
Key Concepts Mastered:
- Model field types and options
- Database relationships and queries
- Migration workflow and best practices
- Admin interface customization
- Template inheritance and pagination
- Git workflow with GitHub
Next week, we'll enhance your blog with advanced features like URL routing, template tags and filters, and implement a commenting system that users can interact with from the frontend. You'll learn about Django's powerful view system and template language in depth!
Before Week 3:
- Ensure your blog is pushed to GitHub
- Practice creating and editing posts through the admin
- Experiment with different admin customizations
- Try adding more sample data to test pagination