// Django Complete Application Template
// Save as: app_name/models.py, views.py, serializers.py, urls.py
// ============================================
// models.py
// ============================================
`
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils import timezone
class User(AbstractUser):
"""Custom user model with additional fields"""
email = models.EmailField(unique=True)
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.email
class Post(models.Model):
"""Blog post model with full-text search support"""
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
('archived', 'Archived'),
]
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True, max_length=255)
content = models.TextField()
excerpt = models.TextField(blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
featured_image = models.ImageField(upload_to='posts/', blank=True)
tags = models.ManyToManyField('Tag', blank=True, related_name='posts')
views = models.PositiveIntegerField(default=0)
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 = ['-published_at', '-created_at']
indexes = [
models.Index(fields=['slug']),
models.Index(fields=['status', 'published_at']),
]
def save(self, *args, **kwargs):
if self.status == 'published' and not self.published_at:
self.published_at = timezone.now()
super().save(*args, **kwargs)
def __str__(self):
return self.title
class Tag(models.Model):
"""Tag model for categorizing posts"""
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(unique=True, max_length=50)
def __str__(self):
return self.name
class Comment(models.Model):
"""Comment model with threading support"""
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, related_name='replies')
content = models.TextField()
is_approved = models.BooleanField(default=True)
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}'
`
// ============================================
// serializers.py
// ============================================
`
from rest_framework import serializers
from .models import User, Post, Tag, Comment
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'email', 'username', 'bio', 'avatar', 'created_at']
read_only_fields = ['created_at']
class UserCreateSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, min_length=8)
class Meta:
model = User
fields = ['email', 'username', 'password']
def create(self, validated_data):
return User.objects.create_user(**validated_data)
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug']
class CommentSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
replies = serializers.SerializerMethodField()
class Meta:
model = Comment
fields = ['id', 'author', 'content', 'parent', 'replies', 'created_at']
read_only_fields = ['created_at']
def get_replies(self, obj):
if obj.replies.exists():
return CommentSerializer(obj.replies.all(), many=True).data
return []
class PostListSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'excerpt', 'author', 'tags', 'views', 'published_at']
class PostDetailSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'content', 'excerpt', 'author',
'tags', 'featured_image', 'views', 'comments', 'created_at', 'published_at']
class PostCreateSerializer(serializers.ModelSerializer):
tags = serializers.PrimaryKeyRelatedField(many=True, queryset=Tag.objects.all(), required=False)
class Meta:
model = Post
fields = ['title', 'slug', 'content', 'excerpt', 'tags', 'status', 'featured_image']
def create(self, validated_data):
tags = validated_data.pop('tags', [])
post = Post.objects.create(**validated_data)
post.tags.set(tags)
return post
`
// ============================================
// views.py
// ============================================
`
from rest_framework import viewsets, permissions, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import User, Post, Tag, Comment
from .serializers import (
UserSerializer, UserCreateSerializer,
PostListSerializer, PostDetailSerializer, PostCreateSerializer,
TagSerializer, CommentSerializer
)
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_serializer_class(self):
if self.action == 'create':
return UserCreateSerializer
return UserSerializer
@action(detail=False, methods=['get'])
def me(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.filter(status='published')
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['status', 'author', 'tags']
search_fields = ['title', 'content']
ordering_fields = ['published_at', 'views', 'created_at']
lookup_field = 'slug'
def get_serializer_class(self):
if self.action == 'list':
return PostListSerializer
if self.action in ['create', 'update', 'partial_update']:
return PostCreateSerializer
return PostDetailSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=['post'])
def increment_views(self, request, slug=None):
post = self.get_object()
post.views += 1
post.save()
return Response({'views': post.views})
class TagViewSet(viewsets.ModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
lookup_field = 'slug'
class CommentViewSet(viewsets.ModelViewSet):
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
return Comment.objects.filter(post__slug=self.kwargs['post_slug'], parent=None)
def perform_create(self, serializer):
post = Post.objects.get(slug=self.kwargs['post_slug'])
serializer.save(author=self.request.user, post=post)
`
// ============================================
// urls.py
// ============================================
`
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework_nested import routers
from . import views
router = DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'posts', views.PostViewSet)
router.register(r'tags', views.TagViewSet)
# Nested router for comments under posts
posts_router = routers.NestedDefaultRouter(router, r'posts', lookup='post')
posts_router.register(r'comments', views.CommentViewSet, basename='post-comments')
urlpatterns = [
path('', include(router.urls)),
path('', include(posts_router.urls)),
]
`