Let's build a real Django app from scratch. We'll create a blog application with posts, comments, and user authentication.
Step 1: Create the Project
Bash
pip install django
django-admin startproject myblog
cd myblog
python manage.py startapp postsStep 2: Configure the App
Python
# myblog/settings.py — add 'posts' to INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'posts',
]Step 3: Define Models
Python
# posts/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
body = models.TextField()
created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(auto_now=True)
published = models.BooleanField(default=False)
tags = models.ManyToManyField('Tag', blank=True, related_name='posts')
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
def get_absolute_url(self):
return f'/posts/{self.slug}/'
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=True)
class Meta:
ordering = ['created']
def __str__(self):
return f'Comment by {self.author} on {self.post}'
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.nameStep 4: Create and Run Migrations
Bash
python manage.py makemigrations posts
python manage.py migrateThis creates the database tables:
| Table | Columns |
|---|---|
posts_post |
id, title, slug, author_id, body, created, updated, published |
posts_comment |
id, post_id, author_id, body, created, active |
posts_tag |
id, name |
posts_post_tags |
post_id, tag_id (junction table) |
Step 5: Register with Admin
Python
# posts/admin.py
from django.contrib import admin
from .models import Post, Comment, Tag
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'created', 'published']
list_filter = ['published', 'created', 'author']
search_fields = ['title', 'body']
prepopulated_fields = {'slug': ('title',)}
raw_id_fields = ['author']
date_hierarchy = 'created'
ordering = ['-created']
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ['author', 'post', 'created', 'active']
list_filter = ['active', 'created']
actions = ['approve_comments']
def approve_comments(self, request, queryset):
queryset.update(active=True)
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ['name']
search_fields = ['name']Step 6: Create Superuser and Test
Bash
python manage.py createsuperuser
# Enter username, email, password
python manage.py runserver
# Visit http://127.0.0.1:8000/admin/💡 Tip: The
related_nameparameter on ForeignKey lets you dopost.comments.all()to get all comments for a post. Choose descriptive names.
Next: URLs and Views