| name | django-expert |
| version | 1.0.0 |
| description | Expert-level Django development for robust Python web applications with ORM, admin, and authentication |
| category | frameworks |
| tags | ["django","python","web","orm","mvc","rest-framework"] |
| allowed-tools | ["Read","Write","Edit","Bash(python:*, django-admin:*, manage.py:*)"] |
Django Expert
Expert guidance for Django - high-level Python web framework for building secure, scalable web applications with batteries included.
Core Concepts
Django Architecture
- MVT (Model-View-Template) pattern
- ORM (Object-Relational Mapping)
- Admin interface
- Authentication system
- URL routing
- Template engine
- Forms and validation
Key Components
- Models (database tables)
- Views (business logic)
- Templates (presentation)
- URLs (routing)
- Forms (user input)
- Middleware (request/response processing)
Project Setup
pip install django
django-admin startproject myproject
cd myproject
python manage.py startapp myapp
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
Models
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
class Post(models.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()
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']
indexes = [
models.Index(fields=['-created_at']),
models.Index(fields=['slug']),
]
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if self.status == 'published' and not self.published_at:
self.published_at = timezone.now()
super().save(*args, **kwargs)
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['created_at']
def __str__(self):
return f'Comment by {self.author} on {self.post}'
Views
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.views.generic import ListView, DetailView, CreateView
from django.contrib import messages
from .models import Post, Comment
from .forms import PostForm, CommentForm
def post_list(request):
posts = Post.objects.filter(status='published').select_related('author')
return render(request, 'blog/post_list.html', {'posts': posts})
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug, status='published')
comments = post.comments.select_related('author')
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.author = request.user
comment.save()
messages.success(request, 'Comment added successfully')
return redirect('post_detail', slug=slug)
else:
form = CommentForm()
return render(request, 'blog/post_detail.html', {
'post': post,
'comments': comments,
'form': form
})
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
return Post.objects.filter(status='published').select_related('author')
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_queryset(self):
return Post.objects.filter(status='published')
class PostCreateView(CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
URLs
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('myapp.urls')),
]
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.PostListView.as_view(), name='post_list'),
path('post/<slug:slug>/', views.post_detail, name='post_detail'),
path('post/create/', views.PostCreateView.as_view(), name='post_create'),
]
Forms
from django import forms
from .models import Post, Comment
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'content', 'status']
widgets = {
'content': forms.Textarea(attrs={'rows': 10}),
}
def clean_slug(self):
slug = self.cleaned_data['slug']
if Post.objects.filter(slug=slug).exists():
raise forms.ValidationError('Slug already exists')
return slug
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['content']
widgets = {
'content': forms.Textarea(attrs={'rows': 3}),
}
Django REST Framework
INSTALLED_APPS = [
...
'rest_framework',
]
from rest_framework import serializers
from .models import Post, Comment
class CommentSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField()
class Meta:
model = Comment
fields = ['id', 'author', 'content', 'created_at']
class PostSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField()
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'author', 'content',
'status', 'created_at', 'comments']
from rest_framework import viewsets, permissions
from rest_framework.decorators import action
from rest_framework.response import Response
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.filter(status='published')
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
post = self.get_object()
post.status = 'published'
post.save()
return Response({'status': 'published'})
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'posts', PostViewSet)
urlpatterns = [
path('api/', include(router.urls)),
]
Admin Interface
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']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'created_at'
actions = ['make_published']
def make_published(self, request, queryset):
queryset.update(status='published')
make_published.short_description = "Mark selected as published"
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ['post', 'author', 'created_at']
list_filter = ['created_at']
search_fields = ['content']
Authentication
from django.contrib.auth import login, logout
from django.contrib.auth.forms import UserCreationForm
def signup(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect('home')
else:
form = UserCreationForm()
return render(request, 'registration/signup.html', {'form': form})
from django.contrib.auth import views as auth_views
urlpatterns = [
path('login/', auth_views.LoginView.as_view(), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('signup/', signup, name='signup'),
]
Testing
from django.test import TestCase, Client
from django.contrib.auth.models import User
from .models import Post
class PostModelTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='test', password='test')
self.post = Post.objects.create(
title='Test Post',
slug='test-post',
author=self.user,
content='Test content',
status='published'
)
def test_post_creation(self):
self.assertEqual(self.post.title, 'Test Post')
self.assertEqual(str(self.post), 'Test Post')
def test_post_slug_unique(self):
with self.assertRaises(Exception):
Post.objects.create(
title='Another Post',
slug='test-post',
author=self.user,
content='Content'
)
class PostViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(username='test', password='test')
def test_post_list_view(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
def test_post_create_requires_auth(self):
response = self.client.get('/post/create/')
self.assertEqual(response.status_code, 302)
self.client.login(username='test', password='test')
response = self.client.get('/post/create/')
self.assertEqual(response.status_code, 200)
Best Practices
- Use select_related/prefetch_related to avoid N+1 queries
- Implement proper validation in forms
- Use class-based views for common patterns
- Protect against CSRF attacks (enabled by default)
- Use Django's built-in authentication
- Implement proper permission checks
- Add database indexes for frequently queried fields
- Use Django migrations for schema changes
Resources