# ── Model Definition ──
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=120, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['name']
verbose_name_plural = 'categories'
def __str__(self):
return self.name
class Post(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
('archived', 'Archived'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=220, unique=True)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
tags = models.ManyToManyField('Tag', blank=True, related_name='posts')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
views_count = models.PositiveIntegerField(default=0)
published_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
indexes = [
models.Index(fields=['status', '-created_at']),
]
def __str__(self):
return self.title
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=60, unique=True)
def __str__(self):
return self.name
# ── QuerySet Operations ──
from .models import Post
# Basic queries
Post.objects.all()
Post.objects.filter(status='published')
Post.objects.exclude(status='draft')
Post.objects.get(pk=1) # raises DoesNotExist if not found
# Field lookups
Post.objects.filter(title__contains='django')
Post.objects.filter(title__icontains='Django') # case-insensitive
Post.objects.filter(title__startswith='How')
Post.objects.filter(views_count__gte=100)
Post.objects.filter(published_at__year=2024)
Post.objects.filter(published_at__date='2024-06-15')
Post.objects.filter(tags__name='python')
Post.objects.filter(author__username='alice')
# Q objects (complex lookups)
from django.db.models import Q
Post.objects.filter(Q(status='published') | Q(status='draft'))
Post.objects.filter(Q(title__icontains='django') & ~Q(author__username='bob'))
# Annotation & Aggregation
from django.db.models import Count, Avg, Max, Min, Sum
Post.objects.annotate(comment_count=Count('comments'))
Post.objects.aggregate(avg_views=Avg('views_count'), total=Count('id'))
# Ordering, Slicing, Distinct
Post.objects.order_by('-published_at')[:10]
Post.objects.order_by('author__username').distinct('author__username')
# Select related (FK, OneToOne) & Prefetch related (M2M, reverse FK)
Post.objects.select_related('author', 'category').prefetch_related('tags')
# Updating
Post.objects.filter(status='draft').update(status='published')
post = Post.objects.get(pk=1)
post.title = 'New Title'
post.save(update_fields=['title', 'updated_at'])
# Deleting
Post.objects.filter(status='archived').delete()
post.delete()
| Field | Description |
|---|
| CharField | Short-to-medium text (max_length required) |
| TextField | Long text (no max_length needed) |
| IntegerField | 32-bit integer |
| BigIntegerField | 64-bit integer |
| FloatField | Floating-point number |
| DecimalField | Exact decimal (max_digits, decimal_places) |
| BooleanField | True/False |
| DateField | Date (auto_now, auto_now_add) |
| DateTimeField | Date + time |
| ForeignKey | Many-to-one relationship |
| ManyToManyField | Many-to-many relationship |
| OneToOneField | One-to-one relationship |
| JSONField | JSON data (PostgreSQL/MySQL) |
| SlugField | URL-friendly string |
| EmailField | Validated email |
| FileField / ImageField | File upload |
| Lookup | SQL Equivalent |
|---|
| __exact | WHERE field = value |
| __iexact | WHERE LOWER(field) = LOWER(value) |
| __contains | WHERE field LIKE %value% |
| __icontains | Case-insensitive contains |
| __startswith | WHERE field LIKE value% |
| __endswith | WHERE field LIKE %value |
| __gt / __gte | WHERE field > / >= value |
| __lt / __lte | WHERE field < / <= value |
| __in | WHERE field IN (list) |
| __range | WHERE field BETWEEN lo AND hi |
| __isnull | WHERE field IS NULL / IS NOT NULL |
| __year / __month / __day | Extract from date fields |
| __regex | Regular expression match |
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, Http404
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Post
from .forms import PostForm
# ── Function-Based View ──
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)
post.views_count += 1
post.save(update_fields=['views_count'])
return render(request, 'blog/post_detail.html', {'post': post})
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.get_absolute_url())
else:
form = PostForm()
return render(request, 'blog/post_form.html', {'form': form})
# ── Class-Based Views ──
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
queryset = Post.objects.filter(status='published')
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
slug_url_kwarg = 'slug'
slug_field = 'slug'
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)
class PostUpdateView(UpdateView):
model = Post
form_class = PostForm
template_name_suffix = '_update_form'
class PostDeleteView(DeleteView):
model = Post
success_url = reverse_lazy('post-list')
# ── API View (JSON) ──
def api_posts(request):
if request.method == 'GET':
posts = Post.objects.filter(status='published').values(
'id', 'title', 'slug', 'views_count', 'published_at'
)
return JsonResponse(list(posts), safe=False)
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
# Function-based views
path('', views.post_list, name='post-list'),
path('post/<slug:slug>/', views.post_detail, name='post-detail'),
path('post/new/', views.post_create, name='post-create'),
# Class-based views
path('cbv/', views.PostListView.as_view(), name='cbv-list'),
path('cbv/<slug:slug>/', views.PostDetailView.as_view(), name='cbv-detail'),
path('cbv/new/', views.PostCreateView.as_view(), name='cbv-create'),
path('cbv/<slug:slug>/edit/', views.PostUpdateView.as_view(), name='cbv-update'),
path('cbv/<slug:slug>/delete/', views.PostDeleteView.as_view(), name='cbv-delete'),
# API
path('api/posts/', views.api_posts, name='api-posts'),
# Include app URLs in project urls.py
# path('blog/', include('blog.urls')),
]
💡Use select_related for ForeignKey/OneToOne (SQL JOIN) and prefetch_related for ManyToMany/reverse ForeignKey (separate queries + Python join). This eliminates the N+1 query problem.
from rest_framework import serializers
from .models import Post, Comment
class PostSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source='author.username', read_only=True)
category_name = serializers.CharField(source='category.name', read_only=True)
tags = serializers.SlugRelatedField(many=True, slug_field='slug', read_only=True)
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'content', 'author_name',
'category_name', 'tags', 'status', 'views_count',
'published_at', 'created_at']
read_only_fields = ['views_count', 'created_at']
class PostCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['title', 'slug', 'content', 'category', 'tags', 'status']
def validate_title(self, value):
if len(value) < 5:
raise serializers.ValidationError('Title must be at least 5 characters.')
return value
from rest_framework import viewsets, permissions, filters, pagination
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from django_filters.rest_framework import DjangoFilterBackend
from .models import Post
from .serializers import PostSerializer, PostCreateSerializer
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.filter(status='published').select_related('author', 'category')
serializer_class = PostSerializer
permission_classes = [IsAuthorOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['category', 'author', 'status']
search_fields = ['title', 'content']
ordering_fields = ['published_at', 'views_count', 'created_at']
pagination_class = pagination.PageNumberPagination
def get_serializer_class(self):
if self.action in ('create', 'update', 'partial_update'):
return PostCreateSerializer
return PostSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=False, methods=['get'])
def drafts(self, request):
posts = self.queryset.filter(status='draft')
page = self.paginate_queryset(posts)
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
# urls.py
# from rest_framework.routers import DefaultRouter
# router = DefaultRouter()
# router.register(r'posts', PostViewSet)
# urlpatterns = router.urls
| ViewSet | Provides |
|---|
| ModelViewSet | list, create, retrieve, update, partial_update, destroy |
| ReadOnlyModelViewSet | list, retrieve only |
| GenericViewSet | Base class with no actions |
| ViewSet | Define actions manually |
| Permission | Behavior |
|---|
| AllowAny | No restrictions (default) |
| IsAuthenticated | Must be logged in |
| IsAdminUser | Must be staff/superuser |
| IsAuthenticatedOrReadOnly | Auth required for writes |
| DjangoModelPermissions | Check model-level permissions |
from django.contrib import admin
from .models import Post, Category, Comment
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'post_count']
search_fields = ['name']
prepopulated_fields = {'slug': ('name',)}
list_per_page = 20
def get_queryset(self, request):
return super().get_queryset(request).annotate(post_count=Count('posts'))
def post_count(self, obj):
return obj.post_count
post_count.admin_order_field = 'post_count'
class PostCommentInline(admin.TabularInline):
model = Comment
extra = 0
readonly_fields = ['created_at']
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'status', 'views_count', 'published_at']
list_filter = ['status', 'category', 'author', 'published_at']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'published_at'
list_editable = ['status']
list_select_related = ['author', 'category']
inlines = [PostCommentInline]
actions = ['publish_posts', 'archive_posts']
readonly_fields = ['created_at', 'updated_at', 'views_count']
@admin.action(description='Publish selected posts')
def publish_posts(self, request, queryset):
updated = queryset.update(status='published')
self.message_user(request, f'{updated} posts published.')
fieldsets = (
('Content', {'fields': ('title', 'slug', 'content', 'author')}),
('Metadata', {'fields': ('category', 'tags', 'status', 'published_at'),
'classes': ('collapse',)}),
)
# ── Django Migrations ──
python manage.py makemigrations # create migration files
python manage.py migrate # apply migrations
python manage.py migrate --fake # mark as applied without running
python manage.py migrate app_name zero # rollback all migrations
python manage.py showmigrations # show migration status
python manage.py sqlmigrate app_name 0001 # show SQL for migration
# ── Data Migrations ──
# In migration file:
from django.db import migrations
def set_default_status(apps, schema_editor):
Post = apps.get_model('blog', 'Post')
Post.objects.filter(status='').update(status='draft')
class Migration(migrations.Migration):
dependencies = [('blog', '0001_initial')]
operations = [
migrations.RunPython(set_default_status),
]
# ── Custom Management Commands ──
# blog/management/commands/seed_posts.py
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Seed the database with sample posts'
def add_arguments(self, parser):
parser.add_argument('--count', type=int, default=10)
def handle(self, *args, **options):
count = options['count']
for i in range(count):
Post.objects.create(title=f'Post {i}', content='Sample content...')
self.stdout.write(self.style.SUCCESS(f'Created {count} posts'))
Q: What is the Django ORM query execution?QuerySets are lazy — they are not evaluated until you iterate, slice, pickle, call repr(), len(), list(), bool(), or use specific methods like .count(), .exists(), .update(), .delete(). This allows Django to optimize queries. Use .iterator() for large datasets to save memory.
Q: select_related vs prefetch_related?select_related does a SQL JOIN for ForeignKey/OneToOne fields (single query). prefetch_related does separate queries and joins in Python for ManyToMany/reverse ForeignKey. Use select_related for forward FK lookups and prefetch_related for M2M and reverse relations.
Q: What are Django signals?Signals allow decoupled apps to get notified when actions occur elsewhere. Built-in signals: pre_save/post_save, pre_delete/post_delete, request_started/finished, user_logged_in/out. Use @receiver decorator. Avoid overusing signals — they make code flow harder to trace.
Q: Explain Django middleware.Middleware is a chain of hooks that process requests/responses globally. Each middleware can process the request before the view and the response after. Examples: AuthenticationMiddleware (adds user to request), CsrfViewMiddleware, SessionMiddleware. Order matters in MIDDLEWARE list.
Q: How does Django handle migrations?Migrations are Django's version control for database schemas. makemigrations creates migration files based on model changes. migrate applies them to the database. Django tracks which migrations have been applied in the django_migrations table. Data migrations use RunPython. Always review generated migrations.
Q: FBV vs CBV in Django?Function-Based Views (FBV): simple, explicit, easy to read, decorators for auth. Class-Based Views (CBV): reusable, extensible via mixins, built-in generic views (ListView, DetailView). Use FBV for simple views, CBV for complex CRUD with shared behavior. Django REST Framework uses ViewSets for API views.
Q: How do you optimize Django performance?1) select_related/prefetch_related to avoid N+1. 2) .only()/.defer() to limit fields. 3) Database indexes on frequently queried fields. 4) Caching (Redis/Memcached) with @cache_page. 5) Connection pooling with django-db-geventpool. 6) Pagination for large querysets. 7) .iterator() for memory-efficient iteration.
Q: What is Django's request/response cycle?URLconf routes request to view. Middleware processes request (auth, CSRF, session). View executes business logic. Template renders response (or JsonResponse). Middleware processes response. Django returns HTTP response to client.
💡Top Django interview topics: ORM queries (lazy evaluation, select_related, prefetch_related), models (fields, Meta, indexes), views (FBV vs CBV), forms/validation, DRF (serializers, viewsets, permissions), migrations, middleware, authentication, caching, and signals.